Skip to content

Commit 0d8d3d6

Browse files
committed
Merge remote-tracking branch 'github-example/master'
2 parents c8dafe0 + a47061d commit 0d8d3d6

File tree

6 files changed

+224
-0
lines changed

6 files changed

+224
-0
lines changed

github/git/Commit.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package git
2+
3+
import scala.collection.mutable.ArrayBuffer
4+
import scala.util.Try
5+
6+
case class Commit(author: String, sha: String, date: String)
7+
object Commit:
8+
def commitsFromJson(json: String): List[Commit] =
9+
for commit <- ujson.read(json).arr.toList
10+
author = Try(commit("author")("login").str)
11+
.orElse(Try(commit("commit")("author")("name").str))
12+
.getOrElse("Unknown")
13+
sha = commit("sha").str
14+
date = Try(commit("commit")("author")("date").str).getOrElse("")
15+
yield
16+
Commit(author, sha, date)
17+

github/git/Contributor.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package git
2+
3+
final case class Contributor(author: String, commits: Int)

github/git/Lang.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package git
2+
3+
enum Lang():
4+
case Scala
5+
case JavaScript
6+
case TypeScript
7+
case Python
8+
case C
9+
case Go
10+
case Yaml
11+
case AsciiDoc
12+
case Markdown
13+
case Shell
14+
case Other
15+
case HTML
16+
case CSS
17+
18+
object Lang:
19+
def fromExtension(ext: String): Lang =
20+
ext match
21+
case "sc" => Scala
22+
case "scala" => Scala
23+
case "js" => JavaScript
24+
case "ts" => TypeScript
25+
case "py" => Python
26+
case "c" => C
27+
case "h" => C
28+
case "go" => Go
29+
case "yml" => Yaml
30+
case "adoc" => AsciiDoc
31+
case "md" => Markdown
32+
case "sh" => Shell
33+
case "html" => HTML
34+
case "css" => CSS
35+
case _ => Other

github/git/Repo.scala

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package git
2+
3+
import sys.error
4+
import sttp.client3._
5+
import sttp.model._
6+
import os.Path
7+
8+
import scala.util.Try
9+
import scala.util.Success
10+
11+
import git.Lang.*
12+
import git.Commit
13+
import git.Contributor
14+
15+
class Repo(val owner: String, val repoName: String):
16+
lazy val localRepo = cloneRepo()
17+
val client = SimpleHttpClient()
18+
19+
def getCommits(page: Int = 1): List[Commit] =
20+
val request = basicRequest
21+
.header("Accept", "application/vnd.github+json")
22+
.get(uri"https://api.github.com/repos/$owner/$repoName/commits?per_page=100&page=$page")
23+
24+
val response = client.send(request)
25+
val json = response.body match
26+
case Right(content) => content
27+
case Left(value) => error(value)
28+
29+
val commits = Commit.commitsFromJson(json)
30+
if (isLast(response)) commits else commits ++ getCommits(page + 1)
31+
32+
def getContributors(): List[Contributor] =
33+
getCommits()
34+
.groupBy(_.author)
35+
.mapValues(seq => seq.length)
36+
.map((author, commits) => Contributor(author, commits))
37+
.toList
38+
39+
def getOpenIssuesWithoutAnswers(page: Int = 1): List[Uri] =
40+
val request = basicRequest
41+
.header("Accept", "application/vnd.github+json")
42+
.get(uri"https://api.github.com/repos/$owner/$repoName/issues?per_page=100&page=$page")
43+
44+
val response = client.send(request)
45+
val json = response.body match
46+
case Right(content) => content
47+
case Left(value) => error(value)
48+
49+
val issues =
50+
for issue <- ujson.read(json).arr.toList
51+
if issue("state").str == "open"
52+
if Try(issue("pull_request")).isFailure
53+
if hasNoComments(issue("comments_url").str)
54+
yield uri"${issue("html_url").str}"
55+
56+
if (isLast(response)) issues else issues ++ getOpenIssuesWithoutAnswers(page + 1)
57+
58+
def lineCountPerLanguage(): Map[Lang, Int] =
59+
os.walk(localRepo, skip = _.last.equals(".git"))
60+
.filter(os.isFile)
61+
.map(path => Lang.fromExtension(path.ext) -> path)
62+
.map((lang, path) => (lang, getLineCount(path)))
63+
.groupBy(_._1)
64+
.mapValues(seq => seq.map(_._2).reduce(_ + _))
65+
.toMap
66+
67+
private def cloneRepo(): Path =
68+
val tmpDir = os.temp.dir()
69+
val address = uri"https://github.com/$owner/$repoName.git"
70+
os.proc("git", "clone", address.toString)
71+
.call(cwd = tmpDir)
72+
tmpDir
73+
74+
private def getLineCount(path: Path): Int =
75+
Try(os.read.lines(path).filter(_.nonEmpty).size) match
76+
case Success(value) => value
77+
case _ => 0
78+
79+
private def hasNoComments(uri: String): Boolean =
80+
val request = basicRequest
81+
.header("Accept", "application/vnd.github+json")
82+
.get(uri"$uri")
83+
84+
val response = client.send(request)
85+
val json = response.body match
86+
case Right(content) => content
87+
case Left(value) => error(value)
88+
89+
ujson.read(json).arr.isEmpty
90+
91+
private def isLast(response: Response[Either[String, String]]) =
92+
response.header(HeaderNames.Link) match
93+
case Some(value) => !value.contains("rel=\"next\"")
94+
case None => true
95+

github/main.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//> using lib "com.softwaremill.sttp.client3::core:3.8.5"
2+
//> using lib "com.lihaoyi::upickle:2.0.0"
3+
//> using lib "com.lihaoyi::os-lib:0.9.0"
4+
//> using lib "org.scalameta::munit::1.0.0-M7"
5+
6+
import sttp.client3._
7+
import sys.error
8+
import git.Repo
9+
import git.Commit
10+
import git.Contributor
11+
import java.nio.file.Path
12+
13+
@main
14+
def main(operation: String, owner: String, repoName: String) =
15+
val repo = Repo(owner, repoName)
16+
operation match
17+
case "contributors" => repo.getContributors()
18+
.sortWith(_.commits > _.commits)
19+
.foreach(c => println(s"$c.author,$c.count"))
20+
case "commits" => repo.getCommits()
21+
.foreach(commit => println(s"$commit.author,$commit.date"))
22+
case "lines" => repo.lineCountPerLanguage()
23+
.foreach((lang, lines) => println(s"$lang,$lines"))
24+
case "issues" => repo.getOpenIssuesWithoutAnswers()
25+
.foreach(println)

github/test/RepoTest.scala

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import munit._
2+
import git._
3+
import git.Lang.*
4+
5+
class RepoTest extends munit.FunSuite:
6+
test("getCommits") {
7+
// given
8+
val repo = Repo("octocat", "Spoon-Knife")
9+
// when
10+
val commitsObtained = repo.getCommits()
11+
// then
12+
val commitsExpected = List(
13+
Commit("octocat", "d0dd1f61b33d64e29d8bc1372a94ef6a2fee76a9", "2014-02-12T23:20:44Z"),
14+
Commit("octocat", "bb4cc8d3b2e14b3af5df699876dd4ff3acd00b7f", "2014-02-04T22:38:36Z"),
15+
Commit("octocat", "a30c19e3f13765a3b48829788bc1cb8b4e95cee4", "2014-02-04T22:38:24Z")
16+
)
17+
assertEquals(commitsObtained, commitsExpected)
18+
}
19+
20+
test("getContributors") {
21+
// given
22+
val repo = Repo("octocat", "Spoon-Knife")
23+
// when
24+
val contributorsObtained = repo.getContributors()
25+
// then
26+
val contributorsExpected = List(
27+
Contributor("octocat", 3)
28+
)
29+
assertEquals(contributorsObtained, contributorsExpected)
30+
}
31+
32+
test("getLineCount") {
33+
// given
34+
val repo = Repo("octocat", "Spoon-Knife")
35+
// when
36+
val langObtained = repo.lineCountPerLanguage()
37+
// then
38+
val langExpected = Map(
39+
Markdown -> 5,
40+
HTML -> 15,
41+
CSS -> 15
42+
)
43+
assertEquals(langObtained, langExpected)
44+
}
45+
46+
test("issues") {
47+
fail("not impemented")
48+
}
49+

0 commit comments

Comments
 (0)