Skip to content

Commit

Permalink
Merge pull request #99 from 2m/cli-project
Browse files Browse the repository at this point in the history
Refactor CLI to a separate project
  • Loading branch information
2m authored Feb 25, 2020
2 parents f5e5130 + 517b438 commit 0833473
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 32 deletions.
8 changes: 8 additions & 0 deletions .scalafix.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
rule = SortImports
SortImports.blocks = [
"java.",
"scala.",
"akka.",
"*",
"lt.dvim.authors.",
]
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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
```
20 changes: 18 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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",
Expand All @@ -63,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")
)
Expand Down
63 changes: 63 additions & 0 deletions cli/src/main/scala/AuthorsCli.scala
Original file line number Diff line number Diff line change
@@ -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 scala.concurrent.Await
import scala.concurrent.duration._

import org.rogach.scallop._

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 <path>] [-r <repo>] <from-ref> <to-ref>
|
|From and to git references can be:
| * commit short hash
| * tag name
| * HEAD
|
|If <path> is not specified, current directory "." is used by default.
|
|If <repo> 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))
}
}
31 changes: 31 additions & 0 deletions cli/src/main/scala/ScallopOps.scala
Original file line number Diff line number Diff line change
@@ -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.")
}
}
}
38 changes: 14 additions & 24 deletions core/src/main/scala/Authors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -27,47 +30,31 @@ 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.{Await, ExecutionContext, Future}
import scala.concurrent.duration._
import lt.dvim.authors.GithubProtocol.{AuthorStats, Commit, Stats}

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:
| <repo> <from> <to> <path>
""".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())
Expand All @@ -82,6 +69,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"))
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/GithubProtocol.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions core/src/test/scala/ParseRepoSpec.scala
Original file line number Diff line number Diff line change
@@ -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"
}
}

}
10 changes: 5 additions & 5 deletions plugin/src/main/scala/AuthorsPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand Down Expand Up @@ -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)
}
}
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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")

0 comments on commit 0833473

Please sign in to comment.