I am trying to learn Kotlin. I am coming from some background in Java. As a learning exercise, I wrote this simple program to summarize occurrences of a string by author email in a list of git repositories.
I am curious if I am approaching things in an idiomatic Kotlin way. Extension functions are awesome but I suspect it's something that I should not overuse.
I plan to make this a command line application and maybe use recursion to get the git repositories from the source directory without explicitly providing them. Before I get too far though I would like to see how I can improve what I have.
Any feedback is appreciated!
import java.io.File
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
import kotlin.concurrent.thread
data class CommitSummary(
var author: String,
var regexString: String,
var count: Int)
data class Commit(
var commit: String = "",
var author: String = "",
var date: OffsetDateTime? = null,
var message: String = "")
fun main(args: Array<String>) {
val cmd = "git log --all --branches=* --remotes=*"
val matchStr = "d'{0,1}oh" //case-insensitive
val gitDir = "C:\\dev\\git\\"
arrayListOf("repo-1", "repo-2")
.forEach { repo ->
thread {
val log = cmd.run(File(gitDir + repo), createTempFile())
val commits = log.parseGitLog()
val summaries = commits.summarizeGitMessages(matchStr)
summaries.forEach { author, summary ->
println(String.format("repo: %s, author: %s, %s count: %s", repo, author, summary.regexString, summary.count))
}
}
}
}
fun File.parseGitLog(): ArrayList<Commit> {
return this.readLines().fold(arrayListOf()) { accumulated, current ->
val startingWord = current.split("\\s".toRegex())[0]
when (startingWord) {
"commit" -> accumulated.add(Commit(current.split("\\s".toRegex())[1]))
"Author:" -> accumulated.last().author = current.substring(current.indexOf("<") + 1, current.indexOf(">"))
"Date:" -> accumulated.last().date = current.split("Date:")[1].trim().parseGitDateString()
else -> accumulated.last().message += current.trim()
}
return@fold accumulated
}
}
fun ArrayList<Commit>.summarizeGitMessages(regexString: String): MutableMap<String, CommitSummary> {
val pattern = Pattern.compile(regexString, Pattern.MULTILINE or Pattern.CASE_INSENSITIVE)
return this.fold(mutableMapOf()) { acc, commit ->
val summary: CommitSummary = acc.getOrPut(commit.author) { CommitSummary(commit.author, regexString, 0) }
val match = pattern.matcher(commit.message)
while (match.find()) {
summary.count = summary.count.plus(1)
}
return@fold acc
}
}
// string extensions
fun String.run(workingDir: File, targetFile: File = File("C:\\dev\\tmpFile")): File {
if (targetFile.exists()) {
targetFile.delete()
}
ProcessBuilder(*split(" ").toTypedArray())
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.appendTo(targetFile))
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()
.waitFor(60, TimeUnit.MINUTES)
return targetFile
}
fun String.parseGitDateString(format: DateTimeFormatter = DateTimeFormatter.ofPattern("EEE MMM d HH:mm:ss yyyy Z")): OffsetDateTime {
return OffsetDateTime.parse(this, format)
}