From 63eb5ce8b253e0a134463edb61f091665cf5431a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Mon, 24 Feb 2020 22:22:52 -0500 Subject: [PATCH 1/2] Separate cli to a project --- CONTRIBUTING.md | 10 ++++ build.sbt | 17 +++++- cli/src/main/scala/AuthorsCli.scala | 63 +++++++++++++++++++++++ cli/src/main/scala/ScallopOps.scala | 31 +++++++++++ core/src/main/scala/Authors.scala | 28 +++------- core/src/test/scala/ParseRepoSpec.scala | 34 ++++++++++++ plugin/src/main/scala/AuthorsPlugin.scala | 2 +- 7 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 cli/src/main/scala/AuthorsCli.scala create mode 100644 cli/src/main/scala/ScallopOps.scala create mode 100644 core/src/test/scala/ParseRepoSpec.scala diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..aed790e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +## Prepare the test repo + +This project uses a prepared Git repository when running some of the tests. +The test git repository is registered as a git submodule. +You will need to initialize and update the submodule before running the tests: + +```sh +git submodule init +git submodule update +``` diff --git a/build.sbt b/build.sbt index 205909a..fb7fd32 100644 --- a/build.sbt +++ b/build.sbt @@ -1,11 +1,13 @@ +val ScalaVersion = "2.12.10" + lazy val authors = project .in(file(".")) - .aggregate(core, plugin) + .aggregate(core, plugin, cli) lazy val core = project .settings( name := "authors-core", - scalaVersion := "2.12.10", + scalaVersion := ScalaVersion, resolvers += Resolver.bintrayRepo("jypma", "maven"), { val Akka = "2.6.3" val AkkaHttp = "10.1.11" @@ -47,6 +49,17 @@ lazy val plugin = project scriptedBufferLog := false ) +lazy val cli = project + .dependsOn(core) + .enablePlugins(AutomateHeaderPlugin) + .settings( + name := "authors-cli", + scalaVersion := ScalaVersion, + libraryDependencies ++= Seq( + "org.rogach" %% "scallop" % "3.3.2" + ) + ) + inThisBuild( Seq( organization := "lt.dvim.authors", diff --git a/cli/src/main/scala/AuthorsCli.scala b/cli/src/main/scala/AuthorsCli.scala new file mode 100644 index 0000000..e35121d --- /dev/null +++ b/cli/src/main/scala/AuthorsCli.scala @@ -0,0 +1,63 @@ +/* + * Copyright 2016 Martynas Mickevičius + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lt.dvim.authors + +import org.rogach.scallop._ + +import scala.concurrent.duration._ +import scala.concurrent.Await + +object AuthorsCli { + import ScallopOpts._ + + class Config(args: Seq[String]) extends ScallopConf(args) { + banner("""|Fetches a summary of authors that contributed to a project between two points in git history. + |Usage: authors [-p ] [-r ] + | + |From and to git references can be: + | * commit short hash + | * tag name + | * HEAD + | + |If is not specified, current directory "." is used by default. + | + |If is not specified, it is parsed from the origin url. + """.stripMargin) + + val path = opt[String](default = Some("."), descr = "Path to the local project directory") + val repo = opt[String](default = None, descr = "org/repo of the project") + val timeout = opt[FiniteDuration](default = Some(30.seconds), descr = "Timeout for the command") + val fromRef = trailArg[String]() + val toRef = trailArg[String]() + + verify() + } + + def main(args: Array[String]) = { + val config = new Config(args.toIndexedSeq) + + val future = + Authors.summary( + config.repo.toOption, + config.fromRef.toOption.get, + config.toRef.toOption.get, + config.path.toOption.get + ) + + println(Await.result(future, config.timeout.toOption.get)) + } +} diff --git a/cli/src/main/scala/ScallopOps.scala b/cli/src/main/scala/ScallopOps.scala new file mode 100644 index 0000000..a5b831d --- /dev/null +++ b/cli/src/main/scala/ScallopOps.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Martynas Mickevičius + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lt.dvim.authors + +import scala.concurrent.duration.Duration +import scala.concurrent.duration.FiniteDuration + +import org.rogach.scallop._ + +object ScallopOpts { + implicit val finiteDurationConverter = singleArgConverter[FiniteDuration] { arg => + Duration(arg) match { + case d: FiniteDuration => d + case d => throw new IllegalArgumentException(s"'$d' is not a FiniteDuration.") + } + } +} diff --git a/core/src/main/scala/Authors.scala b/core/src/main/scala/Authors.scala index f853e14..a095232 100644 --- a/core/src/main/scala/Authors.scala +++ b/core/src/main/scala/Authors.scala @@ -36,38 +36,23 @@ import lt.dvim.authors.GithubProtocol.{AuthorStats, Commit, Stats} import org.eclipse.jgit.storage.file.FileRepositoryBuilder import scala.collection.JavaConverters._ -import scala.concurrent.{Await, ExecutionContext, Future} -import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} object Authors { final val MaxAuthors = 1024 final val GithubApiUrl = "api.github.com" - def main(args: Array[String]) = { - val (repo, from, to, path) = args.toList match { - case repo :: from :: to :: path :: Nil => (repo, from, to, path) - case _ => - println(""" - |Usage: - | - """.stripMargin) - System.exit(1) - ??? - } - - val future = summary(repo, from, to, path) - println(Await.result(future, 30.seconds)) - } - - def summary(repo: String, from: String, to: String, path: String): Future[String] = { + def summary(repo: Option[String], from: String, to: String, path: String): Future[String] = { val cld = classOf[ActorSystem].getClassLoader implicit val sys = ActorSystem("Authors", classLoader = Some(cld)) implicit val gitRepository = Authors.gitRepo(path) implicit val log = Logging(sys, this.getClass) import sys.dispatcher + def parsedRepo = + parseRepo(gitRepository.getConfig().getString("remote", "origin", "url")) - DiffSource(repo, from, to) + DiffSource(repo.getOrElse(parsedRepo), from, to) .via(ActsonReader.instance) .via(ProtocolReader.of(GithubProtocol.compareProto)) .via(StatsAggregator()) @@ -82,6 +67,9 @@ object Authors { } } + def parseRepo(originUrl: String): String = + originUrl.split("github.com").tail.head.drop(1).split(".git").head + def gitRepo(path: String): FileRepository = FileRepositoryBuilder .create(new File(if (path.contains(".git")) path else path + "/.git")) diff --git a/core/src/test/scala/ParseRepoSpec.scala b/core/src/test/scala/ParseRepoSpec.scala new file mode 100644 index 0000000..17a6f1f --- /dev/null +++ b/core/src/test/scala/ParseRepoSpec.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Martynas Mickevičius + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lt.dvim.authors + +import org.scalatest.wordspec.AnyWordSpec +import org.scalatest.matchers.should.Matchers + +class ParseRepoSpec extends AnyWordSpec with Matchers { + + "repo parser" must { + "parse https url" in { + Authors.parseRepo("https://github.com/2m/authors.git") shouldBe "2m/authors" + } + + "parse ssh url" in { + Authors.parseRepo("git@github.com:2m/authors.git") shouldBe "2m/authors" + } + } + +} diff --git a/plugin/src/main/scala/AuthorsPlugin.scala b/plugin/src/main/scala/AuthorsPlugin.scala index f9a33b4..7f57a57 100644 --- a/plugin/src/main/scala/AuthorsPlugin.scala +++ b/plugin/src/main/scala/AuthorsPlugin.scala @@ -106,6 +106,6 @@ object AuthorsPlugin extends AutoPlugin { streams.log.info(s"Fetching authors summary for $repo between $from and $to") - Authors.summary(repo, from, to, baseDirectory.getAbsolutePath) + Authors.summary(Some(repo), from, to, baseDirectory.getAbsolutePath) } } From 517b438266af287dc74bf1fef6e38c879f559b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Mickevi=C4=8Dius?= Date: Mon, 24 Feb 2020 22:27:05 -0500 Subject: [PATCH 2/2] Add scalafix --- .scalafix.conf | 8 ++++++++ .travis.yml | 2 ++ build.sbt | 7 +++++-- cli/src/main/scala/AuthorsCli.scala | 6 +++--- core/src/main/scala/Authors.scala | 12 +++++++----- core/src/main/scala/GithubProtocol.scala | 2 +- plugin/src/main/scala/AuthorsPlugin.scala | 8 ++++---- project/plugins.sbt | 1 + 8 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 .scalafix.conf diff --git a/.scalafix.conf b/.scalafix.conf new file mode 100644 index 0000000..bba46aa --- /dev/null +++ b/.scalafix.conf @@ -0,0 +1,8 @@ +rule = SortImports +SortImports.blocks = [ + "java.", + "scala.", + "akka.", + "*", + "lt.dvim.authors.", +] diff --git a/.travis.yml b/.travis.yml index 7b6a88e..d8263fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,8 @@ jobs: name: "Code style check (fixed with `sbt Test/compile`)" - script: sbt scalafmtSbtCheck || { echo "[error] Unformatted sbt code found. Please run 'scalafmtSbt' and commit the reformatted code."; false; } name: "Build code style check (fixed with `sbt scalafmtSbt`)" + - script: sbt "scalafix --check" || { echo "[error] Unformatted code found. Please run 'scalafix' and commit the reformatted code."; false; } + name: "Code style check (fixed with `sbt scalafix`)" - stage: test script: sbt test diff --git a/build.sbt b/build.sbt index fb7fd32..f346593 100644 --- a/build.sbt +++ b/build.sbt @@ -56,8 +56,8 @@ lazy val cli = project name := "authors-cli", scalaVersion := ScalaVersion, libraryDependencies ++= Seq( - "org.rogach" %% "scallop" % "3.3.2" - ) + "org.rogach" %% "scallop" % "3.3.2" + ) ) inThisBuild( @@ -76,6 +76,9 @@ inThisBuild( ), bintrayOrganization := Some("2m"), scalafmtOnCompile := true, + scalafixDependencies ++= Seq( + "com.nequissimus" %% "sort-imports" % "0.3.1" + ), // show full stack traces and test case durations testOptions in Test += Tests.Argument("-oDF") ) diff --git a/cli/src/main/scala/AuthorsCli.scala b/cli/src/main/scala/AuthorsCli.scala index e35121d..c668f3b 100644 --- a/cli/src/main/scala/AuthorsCli.scala +++ b/cli/src/main/scala/AuthorsCli.scala @@ -16,10 +16,10 @@ package lt.dvim.authors -import org.rogach.scallop._ - -import scala.concurrent.duration._ import scala.concurrent.Await +import scala.concurrent.duration._ + +import org.rogach.scallop._ object AuthorsCli { import ScallopOpts._ diff --git a/core/src/main/scala/Authors.scala b/core/src/main/scala/Authors.scala index a095232..a7fff75 100644 --- a/core/src/main/scala/Authors.scala +++ b/core/src/main/scala/Authors.scala @@ -18,6 +18,9 @@ package lt.dvim.authors import java.io.File +import scala.collection.JavaConverters._ +import scala.concurrent.{ExecutionContext, Future} + import akka.NotUsed import akka.actor.ActorSystem import akka.event.{Logging, LoggingAdapter} @@ -27,16 +30,15 @@ import akka.http.scaladsl.marshalling.PredefinedToRequestMarshallers._ import akka.http.scaladsl.model.{HttpRequest, Uri} import akka.stream.scaladsl.{Flow, Source} import akka.util.ByteString + +import com.madgag.git._ import com.tradeshift.reaktive.marshal.stream.{ActsonReader, ProtocolReader} import org.eclipse.jgit.diff.DiffFormatter import org.eclipse.jgit.internal.storage.file.FileRepository -import org.eclipse.jgit.util.io.DisabledOutputStream -import com.madgag.git._ -import lt.dvim.authors.GithubProtocol.{AuthorStats, Commit, Stats} import org.eclipse.jgit.storage.file.FileRepositoryBuilder +import org.eclipse.jgit.util.io.DisabledOutputStream -import scala.collection.JavaConverters._ -import scala.concurrent.{ExecutionContext, Future} +import lt.dvim.authors.GithubProtocol.{AuthorStats, Commit, Stats} object Authors { final val MaxAuthors = 1024 diff --git a/core/src/main/scala/GithubProtocol.scala b/core/src/main/scala/GithubProtocol.scala index d17ad7c..6673a4e 100644 --- a/core/src/main/scala/GithubProtocol.scala +++ b/core/src/main/scala/GithubProtocol.scala @@ -18,8 +18,8 @@ package lt.dvim.authors import com.tradeshift.reaktive.json.JSONProtocol._ import com.tradeshift.reaktive.marshal.Protocol._ -import lt.dvim.scala.compat.vavr.OptionConverters._ import io.vavr.control.{Option => VavrOption} +import lt.dvim.scala.compat.vavr.OptionConverters._ object GithubProtocol { final case class GithubAuthor(login: String, url: String, avatar: String) diff --git a/plugin/src/main/scala/AuthorsPlugin.scala b/plugin/src/main/scala/AuthorsPlugin.scala index 7f57a57..68a5969 100644 --- a/plugin/src/main/scala/AuthorsPlugin.scala +++ b/plugin/src/main/scala/AuthorsPlugin.scala @@ -16,13 +16,13 @@ package lt.dvim.authors -import sbt._ +import scala.concurrent.duration._ +import scala.concurrent.{Await, Future} + import sbt.Keys._ +import sbt._ import sbt.complete.DefaultParsers._ -import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ - object AuthorsPlugin extends AutoPlugin { object autoImport extends AuthorsKeys import autoImport._ diff --git a/project/plugins.sbt b/project/plugins.sbt index 7cb5ece..8761e9b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,3 +3,4 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.3.1") addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.6") addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.11") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.4.0") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.11")