All Downloads are FREE. Search and download functionalities are using the official Maven repository.

coursier.cli.scaladex.Scaladex.scala Maven / Gradle / Ivy

The newest version!
package coursier.cli.scaladex

import java.net.HttpURLConnection
import java.nio.charset.StandardCharsets
import java.util.concurrent.ExecutorService

import argonaut._, Argonaut._, ArgonautShapeless._
import coursier.core.{ Artifact, Attributes }
import coursier.{ Fetch, Module }

import scala.language.higherKinds
import scalaz.{ -\/, EitherT, Monad, Nondeterminism, \/, \/- }
import scalaz.Scalaz.ToEitherOps
import scalaz.Scalaz.ToEitherOpsFromEither
import scalaz.concurrent.Task
import scalaz.std.list._

object Scaladex {

  case class SearchResult(
    /** GitHub organization */
    organization: String,
    /** GitHub repository */
    repository: String,
    /** Scaladex artifact names */
    artifacts: List[String] = Nil
  )

  case class ArtifactInfos(
    /** Dependency group ID (aka organization) */
    groupId: String,
    /** Dependency artifact ID (aka name or module name) */
    artifactId: String,
    /** Dependency version */
    version: String
  )

  def apply(pool: ExecutorService): Scaladex[Task] =
    Scaladex({ url =>
      EitherT(Task({
        var conn: HttpURLConnection = null

        val b = try {
          conn = new java.net.URL(url).openConnection().asInstanceOf[HttpURLConnection]
          coursier.Platform.readFullySync(conn.getInputStream)
        } finally {
          if (conn != null)
            coursier.Cache.closeConn(conn)
        }

        new String(b, StandardCharsets.UTF_8).right[String]
      })(pool))
    }, Nondeterminism[Task])

  def cached(fetch: Fetch.Content[Task]*): Scaladex[Task] =
    Scaladex({
      url =>
        def get(fetch: Fetch.Content[Task]) =
          fetch(
            Artifact(url, Map(), Map(), Attributes("", ""), changing = true, None)
          )

        (get(fetch.head) /: fetch.tail)(_ orElse get(_))
    }, Nondeterminism[Task])
}

// TODO Add F[_] type param, change `fetch` type to `String => EitherT[F, String, String]`, adjust method signatures accordingly, ...
case class Scaladex[F[_]](fetch: String => EitherT[F, String, String], F: Nondeterminism[F]) {

  private implicit def F0 = F

  // quick & dirty API for querying scaladex

  def search(name: String, target: String, scalaVersion: String): EitherT[F, String, Seq[Scaladex.SearchResult]] = {

    val s = fetch(
      // FIXME Escaping
      s"https://index.scala-lang.org/api/search?q=$name&target=$target&scalaVersion=$scalaVersion"
    )

    s.flatMap(s => EitherT.fromDisjunction[F](s.decodeEither[List[Scaladex.SearchResult]].disjunction))
  }

  /**
    *
    * @param organization: GitHub organization
    * @param repository: GitHub repository name
    * @param artifactName: Scaladex artifact name
    * @return
    */
  def artifactInfos(organization: String, repository: String, artifactName: String): EitherT[F, String, Scaladex.ArtifactInfos] = {

    val s = fetch(
      // FIXME Escaping
      s"https://index.scala-lang.org/api/project?organization=$organization&repository=$repository&artifact=$artifactName"
    )

    s.flatMap(s => EitherT.fromDisjunction[F](s.decodeEither[Scaladex.ArtifactInfos].disjunction))
  }

  /**
    *
    * @param organization: GitHub organization
    * @param repository: GitHub repository name
    * @return
    */
  def artifactNames(organization: String, repository: String): EitherT[F, String, Seq[String]] = {

    val s = fetch(
      // FIXME Escaping
      s"https://index.scala-lang.org/api/project?organization=$organization&repository=$repository"
    )

    case class Result(artifacts: List[String])

    s.flatMap(s => EitherT.fromDisjunction[F](s.decodeEither[Result].disjunction.map(_.artifacts)))
  }


  /**
    * Modules / versions known to the Scaladex
    *
    * Latest version only.
    */
  def dependencies(name: String, scalaVersion: String, logger: String => Unit): EitherT[F, String, Seq[(Module, String)]] = {
    val idx = name.indexOf('/')
    val orgNameOrError =
      if (idx >= 0) {
        val org = name.take(idx)
        val repo = name.drop(idx + 1)

        artifactNames(org, repo).map((org, repo, _)): EitherT[F, String, (String, String, Seq[String])]
      } else
        search(name, "JVM", scalaVersion) // FIXME Don't hardcode
          .flatMap {
            case Seq(first, _*) =>
              logger(s"Using ${first.organization}/${first.repository} for $name")
              EitherT.fromDisjunction[F]((first.organization, first.repository, first.artifacts).right): EitherT[F, String, (String, String, Seq[String])]
            case Seq() =>
              EitherT.fromDisjunction[F](s"No project found for $name".left): EitherT[F, String, (String, String, Seq[String])]
          }

    orgNameOrError.flatMap {
      case (ghOrg, ghRepo, artifactNames) =>

        val moduleVersions = F.map(F.gather(artifactNames.map { artifactName =>
          F.map(artifactInfos(ghOrg, ghRepo, artifactName).run) {
            case -\/(err) =>
              logger(s"Cannot get infos about artifact $artifactName from $ghOrg/$ghRepo: $err, ignoring it")
              Nil
            case \/-(infos) =>
              logger(s"Found module ${infos.groupId}:${infos.artifactId}:${infos.version}")
              Seq(Module(infos.groupId, infos.artifactId) -> infos.version)
          }
        }))(_.flatten)

        EitherT(F.map(moduleVersions) { l =>
          if (l.isEmpty)
            s"No module found for $ghOrg/$ghRepo".left
          else
            l.right
        })
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy