-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(analyzer): rewrite monadic version with tasks
- Loading branch information
Showing
20 changed files
with
143 additions
and
267 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
.idea/modules/analyzer-monadic/direct-style-experiments.analyzer-monadic.test.iml
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
analyzer-monadic/src/main/scala/io/github/tassiLuca/analyzer/client/AppController.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package io.github.tassiLuca.analyzer.client | ||
|
||
import io.github.tassiLuca.analyzer.commons.client.{AnalyzerView, AppController, OrganizationReport} | ||
import io.github.tassiLuca.analyzer.commons.lib.RepositoryReport | ||
import io.github.tassiLuca.analyzer.lib.Analyzer | ||
import monix.execution.CancelableFuture | ||
|
||
object AppController: | ||
def monadic: AppController = MonadicAppController() | ||
|
||
private class MonadicAppController extends AppController: | ||
|
||
import monix.execution.Scheduler.Implicits.global | ||
private val view = AnalyzerView.gui(this) | ||
private val analyzer = Analyzer.ofGitHub() | ||
private var currentComputation: Option[CancelableFuture[Unit]] = None | ||
|
||
view.run() | ||
|
||
override def runSession(organizationName: String): Unit = | ||
var organizationReport: OrganizationReport = (Map(), Set()) | ||
val f = analyzer.analyze(organizationName) { report => | ||
organizationReport = (organizationReport._1.aggregatedTo(report), organizationReport._2 + report) | ||
view.update(organizationReport) | ||
}.value.runToFuture.map { case Left(value) => view.error(value); case Right(_) => view.endComputation() } | ||
currentComputation = Some(f) | ||
|
||
override def stopSession(): Unit = currentComputation foreach (_.cancel()) | ||
|
||
extension (m: Map[String, Long]) | ||
private def aggregatedTo(report: RepositoryReport): Map[String, Long] = | ||
m ++ report.contributions.map(c => c.user -> (m.getOrElse(c.user, 0L) + c.contributions)) |
4 changes: 4 additions & 0 deletions
4
analyzer-monadic/src/main/scala/io/github/tassiLuca/analyzer/client/Launcher.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package io.github.tassiLuca.analyzer.client | ||
|
||
@main def monadicAnalyzerLauncher(): Unit = | ||
AppController.monadic |
62 changes: 14 additions & 48 deletions
62
analyzer-monadic/src/main/scala/io/github/tassiLuca/analyzer/lib/Analyzer.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,36 @@ | ||
package io.github.tassiLuca.analyzer.lib | ||
|
||
import cats.data.EitherT | ||
import cats.implicits.toTraverseOps | ||
import io.github.tassiLuca.analyzer.commons.lib | ||
import io.github.tassiLuca.analyzer.commons.lib.{Repository, RepositoryReport} | ||
|
||
import scala.concurrent.{Await, ExecutionContext, Future} | ||
import monix.eval.Task | ||
|
||
trait Analyzer: | ||
def analyze(organizationName: String)( | ||
updateResult: RepositoryReport => Unit, | ||
)(using ExecutionContext): Future[Either[String, Seq[RepositoryReport]]] | ||
|
||
def analyze2(organizationName: String)( | ||
updateResult: RepositoryReport => Unit, | ||
)(using ExecutionContext): EitherT[Future, String, Seq[RepositoryReport]] | ||
): EitherT[Task, String, Seq[RepositoryReport]] | ||
|
||
object Analyzer: | ||
def ofGitHub(): Analyzer = new AnalyzerImpl() | ||
def ofGitHub(): Analyzer = AnalyzerImpl() | ||
|
||
private class AnalyzerImpl extends Analyzer: | ||
private val gitHubService = GitHubService() | ||
|
||
override def analyze(organizationName: String)( | ||
updateResult: RepositoryReport => Unit, | ||
)(using ExecutionContext): Future[Either[String, Seq[RepositoryReport]]] = | ||
gitHubService.repositoriesOf(organizationName).flatMap { | ||
case Left(error) => Future.successful(Left(error)) | ||
case Right(repos) => | ||
val futuresReports: Seq[Future[RepositoryReport]] = repos.map(performAnalysis) | ||
val futureSeqOfReports: Future[Seq[RepositoryReport]] = Future.sequence(futuresReports) | ||
futureSeqOfReports.map(Right(_)) | ||
} | ||
|
||
override def analyze2(organizationName: String)( | ||
updateResult: RepositoryReport => Unit, | ||
)(using ExecutionContext): EitherT[Future, String, Seq[RepositoryReport]] = | ||
import cats.implicits.toTraverseOps | ||
): EitherT[Task, String, Seq[RepositoryReport]] = | ||
for | ||
repositories <- EitherT(gitHubService.repositoriesOf(organizationName)) | ||
reports <- repositories.traverse(r => EitherT.right(r.idiomaticAnalysis)) | ||
repositories <- gitHubService.repositoriesOf(organizationName) | ||
reports <- repositories.traverse(r => EitherT.right(r.performAnalysis(updateResult))) | ||
yield reports | ||
|
||
extension (r: Repository) | ||
private def performAnalysis(using ExecutionContext): Future[RepositoryReport] = | ||
val contributionsTask = gitHubService.contributorsOf(r.organization, r.name) | ||
val releaseTask = gitHubService.lastReleaseOf(r.organization, r.name) | ||
private def performAnalysis(updateResult: RepositoryReport => Unit): Task[RepositoryReport] = | ||
val contributorsTask = gitHubService.contributorsOf(r.organization, r.name).value | ||
val releaseTask = gitHubService.lastReleaseOf(r.organization, r.name).value | ||
for | ||
contributions <- contributionsTask | ||
lastRelease <- releaseTask | ||
yield lib.RepositoryReport(r.name, r.issues, r.stars, contributions.getOrElse(Seq.empty), lastRelease.toOption) | ||
|
||
private def idiomaticAnalysis(using ExecutionContext): Future[RepositoryReport] = | ||
import cats.implicits.catsSyntaxTuple2Semigroupal | ||
(gitHubService.contributorsOf(r.organization, r.name), gitHubService.lastReleaseOf(r.organization, r.name)) | ||
.mapN { case (contributions, lastRelease) => | ||
lib.RepositoryReport(r.name, r.issues, r.stars, contributions.getOrElse(Seq.empty), lastRelease.toOption) | ||
} | ||
|
||
@main def testAnalyzer(): Unit = | ||
given ExecutionContext = ExecutionContext.global | ||
val result = Analyzer.ofGitHub().analyze("unibo-spe")(report => println(report)) | ||
Await.ready(result, scala.concurrent.duration.Duration.Inf) | ||
println(s">> $result") | ||
|
||
@main def testAnalyzerWithCats(): Unit = | ||
given ExecutionContext = ExecutionContext.global | ||
val result = Analyzer.ofGitHub().analyze2("unibo-spe")(report => println(report)).value | ||
Await.ready(result, scala.concurrent.duration.Duration.Inf) | ||
println(s">> $result") | ||
result <- Task.parZip2(contributorsTask, releaseTask) | ||
report = RepositoryReport(r.name, r.issues, r.stars, result._1.getOrElse(Seq.empty), result._2.toOption) | ||
_ <- Task(updateResult(report)) | ||
yield report |
54 changes: 16 additions & 38 deletions
54
analyzer-monadic/src/main/scala/io/github/tassiLuca/analyzer/lib/GitHubService.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,44 @@ | ||
package io.github.tassiLuca.analyzer.lib | ||
|
||
import cats.data.EitherT | ||
import io.github.tassiLuca.analyzer.commons.lib.{Contribution, Release, Repository} | ||
import sttp.client3.HttpClientFutureBackend | ||
import sttp.model.Uri | ||
|
||
import scala.concurrent.{Await, ExecutionContext, Future} | ||
import monix.eval.Task | ||
|
||
trait GitHubService: | ||
def repositoriesOf(organizationName: String)(using ExecutionContext): Future[Either[String, Seq[Repository]]] | ||
|
||
def contributorsOf( | ||
organizationName: String, | ||
repositoryName: String, | ||
)(using ExecutionContext): Future[Either[String, Seq[Contribution]]] | ||
|
||
def lastReleaseOf( | ||
organizationName: String, | ||
repositoryName: String, | ||
)(using ExecutionContext): Future[Either[String, Release]] | ||
def repositoriesOf(organizationName: String): EitherT[Task, String, Seq[Repository]] | ||
def contributorsOf(organizationName: String, repositoryName: String): EitherT[Task, String, Seq[Contribution]] | ||
def lastReleaseOf(organizationName: String, repositoryName: String): EitherT[Task, String, Release] | ||
|
||
object GitHubService: | ||
def apply(): GitHubService = GitHubServiceImpl() | ||
|
||
private class GitHubServiceImpl extends GitHubService: | ||
import sttp.client3.httpclient.monix.HttpClientMonixBackend | ||
import sttp.client3.{UriContext, basicRequest} | ||
import upickle.default.{read, Reader} | ||
import sttp.model.Uri | ||
import upickle.default.{Reader, read} | ||
|
||
private val apiUrl = "https://api.github.com" | ||
private val request = basicRequest.auth.bearer(System.getenv("GH_TOKEN")) | ||
|
||
override def repositoriesOf( | ||
organizationName: String, | ||
)(using ExecutionContext): Future[Either[String, Seq[Repository]]] = | ||
performRequest[Seq[Repository]](uri"$apiUrl/orgs/$organizationName/repos?per_page=100") | ||
override def repositoriesOf(organizationName: String): EitherT[Task, String, Seq[Repository]] = | ||
performRequest[Seq[Repository]](uri"$apiUrl/orgs/$organizationName/repos") | ||
|
||
override def contributorsOf( | ||
organizationName: String, | ||
repositoryName: String, | ||
)(using ExecutionContext): Future[Either[String, Seq[Contribution]]] = | ||
performRequest[Seq[Contribution]](uri"$apiUrl/repos/$organizationName/$repositoryName/contributors?per_page=100") | ||
): EitherT[Task, String, Seq[Contribution]] = | ||
performRequest[Seq[Contribution]](uri"$apiUrl/repos/$organizationName/$repositoryName/contributors") | ||
|
||
override def lastReleaseOf( | ||
organizationName: String, | ||
repositoryName: String, | ||
)(using ExecutionContext): Future[Either[String, Release]] = | ||
): EitherT[Task, String, Release] = | ||
performRequest[Release](uri"$apiUrl/repos/$organizationName/$repositoryName/releases/latest") | ||
|
||
private def performRequest[T](endpoint: Uri)(using Reader[T], ExecutionContext): Future[Either[String, T]] = | ||
private def performRequest[T](endpoint: Uri)(using Reader[T]): EitherT[Task, String, T] = EitherT: | ||
for | ||
response <- HttpClientFutureBackend().send(request.get(endpoint)) | ||
backend <- HttpClientMonixBackend() | ||
response <- request.get(endpoint).send(backend) | ||
result = response.body.map(r => read[T](r)) | ||
yield result | ||
|
||
@main def main(): Unit = | ||
given ExecutionContext = ExecutionContext.global | ||
val service = GitHubService() | ||
val result = service.repositoriesOf("unibo-spe") | ||
Await.ready(result, scala.concurrent.duration.Duration.Inf) | ||
println(result.value) | ||
val result2 = service.contributorsOf("unibo-spe", "spe-slides") | ||
Await.ready(result2, scala.concurrent.duration.Duration.Inf) | ||
println(result2.value) | ||
val result3 = service.lastReleaseOf("unibo-spe", "spe-slides") | ||
Await.ready(result3, scala.concurrent.duration.Duration.Inf) | ||
println(result3.value) |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.