Skip to content

Commit 0833473

Browse files
authored
Merge pull request #99 from 2m/cli-project
Refactor CLI to a separate project
2 parents f5e5130 + 517b438 commit 0833473

11 files changed

Lines changed: 187 additions & 32 deletions

File tree

.scalafix.conf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
rule = SortImports
2+
SortImports.blocks = [
3+
"java.",
4+
"scala.",
5+
"akka.",
6+
"*",
7+
"lt.dvim.authors.",
8+
]

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ jobs:
1212
name: "Code style check (fixed with `sbt Test/compile`)"
1313
- script: sbt scalafmtSbtCheck || { echo "[error] Unformatted sbt code found. Please run 'scalafmtSbt' and commit the reformatted code."; false; }
1414
name: "Build code style check (fixed with `sbt scalafmtSbt`)"
15+
- script: sbt "scalafix --check" || { echo "[error] Unformatted code found. Please run 'scalafix' and commit the reformatted code."; false; }
16+
name: "Code style check (fixed with `sbt scalafix`)"
1517

1618
- stage: test
1719
script: sbt test

CONTRIBUTING.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## Prepare the test repo
2+
3+
This project uses a prepared Git repository when running some of the tests.
4+
The test git repository is registered as a git submodule.
5+
You will need to initialize and update the submodule before running the tests:
6+
7+
```sh
8+
git submodule init
9+
git submodule update
10+
```

build.sbt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
val ScalaVersion = "2.12.10"
2+
13
lazy val authors = project
24
.in(file("."))
3-
.aggregate(core, plugin)
5+
.aggregate(core, plugin, cli)
46

57
lazy val core = project
68
.settings(
79
name := "authors-core",
8-
scalaVersion := "2.12.10",
10+
scalaVersion := ScalaVersion,
911
resolvers += Resolver.bintrayRepo("jypma", "maven"), {
1012
val Akka = "2.6.3"
1113
val AkkaHttp = "10.1.11"
@@ -47,6 +49,17 @@ lazy val plugin = project
4749
scriptedBufferLog := false
4850
)
4951

52+
lazy val cli = project
53+
.dependsOn(core)
54+
.enablePlugins(AutomateHeaderPlugin)
55+
.settings(
56+
name := "authors-cli",
57+
scalaVersion := ScalaVersion,
58+
libraryDependencies ++= Seq(
59+
"org.rogach" %% "scallop" % "3.3.2"
60+
)
61+
)
62+
5063
inThisBuild(
5164
Seq(
5265
organization := "lt.dvim.authors",
@@ -63,6 +76,9 @@ inThisBuild(
6376
),
6477
bintrayOrganization := Some("2m"),
6578
scalafmtOnCompile := true,
79+
scalafixDependencies ++= Seq(
80+
"com.nequissimus" %% "sort-imports" % "0.3.1"
81+
),
6682
// show full stack traces and test case durations
6783
testOptions in Test += Tests.Argument("-oDF")
6884
)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2016 Martynas Mickevičius
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package lt.dvim.authors
18+
19+
import scala.concurrent.Await
20+
import scala.concurrent.duration._
21+
22+
import org.rogach.scallop._
23+
24+
object AuthorsCli {
25+
import ScallopOpts._
26+
27+
class Config(args: Seq[String]) extends ScallopConf(args) {
28+
banner("""|Fetches a summary of authors that contributed to a project between two points in git history.
29+
|Usage: authors [-p <path>] [-r <repo>] <from-ref> <to-ref>
30+
|
31+
|From and to git references can be:
32+
| * commit short hash
33+
| * tag name
34+
| * HEAD
35+
|
36+
|If <path> is not specified, current directory "." is used by default.
37+
|
38+
|If <repo> is not specified, it is parsed from the origin url.
39+
""".stripMargin)
40+
41+
val path = opt[String](default = Some("."), descr = "Path to the local project directory")
42+
val repo = opt[String](default = None, descr = "org/repo of the project")
43+
val timeout = opt[FiniteDuration](default = Some(30.seconds), descr = "Timeout for the command")
44+
val fromRef = trailArg[String]()
45+
val toRef = trailArg[String]()
46+
47+
verify()
48+
}
49+
50+
def main(args: Array[String]) = {
51+
val config = new Config(args.toIndexedSeq)
52+
53+
val future =
54+
Authors.summary(
55+
config.repo.toOption,
56+
config.fromRef.toOption.get,
57+
config.toRef.toOption.get,
58+
config.path.toOption.get
59+
)
60+
61+
println(Await.result(future, config.timeout.toOption.get))
62+
}
63+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2016 Martynas Mickevičius
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package lt.dvim.authors
18+
19+
import scala.concurrent.duration.Duration
20+
import scala.concurrent.duration.FiniteDuration
21+
22+
import org.rogach.scallop._
23+
24+
object ScallopOpts {
25+
implicit val finiteDurationConverter = singleArgConverter[FiniteDuration] { arg =>
26+
Duration(arg) match {
27+
case d: FiniteDuration => d
28+
case d => throw new IllegalArgumentException(s"'$d' is not a FiniteDuration.")
29+
}
30+
}
31+
}

core/src/main/scala/Authors.scala

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ package lt.dvim.authors
1818

1919
import java.io.File
2020

21+
import scala.collection.JavaConverters._
22+
import scala.concurrent.{ExecutionContext, Future}
23+
2124
import akka.NotUsed
2225
import akka.actor.ActorSystem
2326
import akka.event.{Logging, LoggingAdapter}
@@ -27,47 +30,31 @@ import akka.http.scaladsl.marshalling.PredefinedToRequestMarshallers._
2730
import akka.http.scaladsl.model.{HttpRequest, Uri}
2831
import akka.stream.scaladsl.{Flow, Source}
2932
import akka.util.ByteString
33+
34+
import com.madgag.git._
3035
import com.tradeshift.reaktive.marshal.stream.{ActsonReader, ProtocolReader}
3136
import org.eclipse.jgit.diff.DiffFormatter
3237
import org.eclipse.jgit.internal.storage.file.FileRepository
33-
import org.eclipse.jgit.util.io.DisabledOutputStream
34-
import com.madgag.git._
35-
import lt.dvim.authors.GithubProtocol.{AuthorStats, Commit, Stats}
3638
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
39+
import org.eclipse.jgit.util.io.DisabledOutputStream
3740

38-
import scala.collection.JavaConverters._
39-
import scala.concurrent.{Await, ExecutionContext, Future}
40-
import scala.concurrent.duration._
41+
import lt.dvim.authors.GithubProtocol.{AuthorStats, Commit, Stats}
4142

4243
object Authors {
4344
final val MaxAuthors = 1024
4445
final val GithubApiUrl = "api.github.com"
4546

46-
def main(args: Array[String]) = {
47-
val (repo, from, to, path) = args.toList match {
48-
case repo :: from :: to :: path :: Nil => (repo, from, to, path)
49-
case _ =>
50-
println("""
51-
|Usage:
52-
| <repo> <from> <to> <path>
53-
""".stripMargin)
54-
System.exit(1)
55-
???
56-
}
57-
58-
val future = summary(repo, from, to, path)
59-
println(Await.result(future, 30.seconds))
60-
}
61-
62-
def summary(repo: String, from: String, to: String, path: String): Future[String] = {
47+
def summary(repo: Option[String], from: String, to: String, path: String): Future[String] = {
6348
val cld = classOf[ActorSystem].getClassLoader
6449
implicit val sys = ActorSystem("Authors", classLoader = Some(cld))
6550
implicit val gitRepository = Authors.gitRepo(path)
6651
implicit val log = Logging(sys, this.getClass)
6752

6853
import sys.dispatcher
54+
def parsedRepo =
55+
parseRepo(gitRepository.getConfig().getString("remote", "origin", "url"))
6956

70-
DiffSource(repo, from, to)
57+
DiffSource(repo.getOrElse(parsedRepo), from, to)
7158
.via(ActsonReader.instance)
7259
.via(ProtocolReader.of(GithubProtocol.compareProto))
7360
.via(StatsAggregator())
@@ -82,6 +69,9 @@ object Authors {
8269
}
8370
}
8471

72+
def parseRepo(originUrl: String): String =
73+
originUrl.split("github.com").tail.head.drop(1).split(".git").head
74+
8575
def gitRepo(path: String): FileRepository =
8676
FileRepositoryBuilder
8777
.create(new File(if (path.contains(".git")) path else path + "/.git"))

core/src/main/scala/GithubProtocol.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ package lt.dvim.authors
1818

1919
import com.tradeshift.reaktive.json.JSONProtocol._
2020
import com.tradeshift.reaktive.marshal.Protocol._
21-
import lt.dvim.scala.compat.vavr.OptionConverters._
2221
import io.vavr.control.{Option => VavrOption}
22+
import lt.dvim.scala.compat.vavr.OptionConverters._
2323

2424
object GithubProtocol {
2525
final case class GithubAuthor(login: String, url: String, avatar: String)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2016 Martynas Mickevičius
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package lt.dvim.authors
18+
19+
import org.scalatest.wordspec.AnyWordSpec
20+
import org.scalatest.matchers.should.Matchers
21+
22+
class ParseRepoSpec extends AnyWordSpec with Matchers {
23+
24+
"repo parser" must {
25+
"parse https url" in {
26+
Authors.parseRepo("https://github.com/2m/authors.git") shouldBe "2m/authors"
27+
}
28+
29+
"parse ssh url" in {
30+
Authors.parseRepo("git@github.com:2m/authors.git") shouldBe "2m/authors"
31+
}
32+
}
33+
34+
}

plugin/src/main/scala/AuthorsPlugin.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616

1717
package lt.dvim.authors
1818

19-
import sbt._
19+
import scala.concurrent.duration._
20+
import scala.concurrent.{Await, Future}
21+
2022
import sbt.Keys._
23+
import sbt._
2124
import sbt.complete.DefaultParsers._
2225

23-
import scala.concurrent.{Await, Future}
24-
import scala.concurrent.duration._
25-
2626
object AuthorsPlugin extends AutoPlugin {
2727
object autoImport extends AuthorsKeys
2828
import autoImport._
@@ -106,6 +106,6 @@ object AuthorsPlugin extends AutoPlugin {
106106

107107
streams.log.info(s"Fetching authors summary for $repo between $from and $to")
108108

109-
Authors.summary(repo, from, to, baseDirectory.getAbsolutePath)
109+
Authors.summary(Some(repo), from, to, baseDirectory.getAbsolutePath)
110110
}
111111
}

0 commit comments

Comments
 (0)