Formatting

This commit is contained in:
Matt Soucy 2020-08-18 21:01:00 -04:00
parent b85099fbbd
commit 1159b87a53
10 changed files with 240 additions and 258 deletions

View File

@ -1,21 +1,20 @@
package me.msoucy.gbat package me.msoucy.gbat
import java.io.File import java.io.File
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import me.msoucy.gbat.models.CondensedAnalysis
import me.msoucy.gbat.models.Condensation import me.msoucy.gbat.models.Condensation
import me.msoucy.gbat.models.CondensedAnalysis
import me.msoucy.gbat.models.KnowledgeModel import me.msoucy.gbat.models.KnowledgeModel
import me.msoucy.gbat.models.LineModel import me.msoucy.gbat.models.LineModel
import me.msoucy.gbat.models.RiskModel import me.msoucy.gbat.models.RiskModel
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
fun analyze( fun analyze(
riskModel : RiskModel, riskModel: RiskModel,
createdConstant : Double, createdConstant: Double,
historyItem : HistoryItem, historyItem: HistoryItem,
verbose : Boolean = false verbose: Boolean = false
) : CondensedAnalysis { ): CondensedAnalysis {
val lineModel = LineModel() val lineModel = LineModel()
val db = Database.connect("jdbc:sqlite::memory:", "org.sqlite.JDBC") val db = Database.connect("jdbc:sqlite::memory:", "org.sqlite.JDBC")
return transaction(db) { return transaction(db) {
@ -25,8 +24,8 @@ fun analyze(
historyItem.authorDiffs.forEach { (author, changes) -> historyItem.authorDiffs.forEach { (author, changes) ->
changes.forEach { change -> changes.forEach { change ->
changesProcessed++ changesProcessed++
if(changesProcessed % 1000 == 0 && verbose) { if (changesProcessed % 1000 == 0 && verbose) {
System.err.println("Analyzer applied change #${changesProcessed}") System.err.println("Analyzer applied change #$changesProcessed")
} }
lineModel.apply(change.eventType, change.lineNum, change.lineVal ?: "") lineModel.apply(change.eventType, change.lineNum, change.lineVal ?: "")
knowledgeModel.apply(change.eventType, author, change.lineNum) knowledgeModel.apply(change.eventType, author, change.lineNum)
@ -44,21 +43,21 @@ fun analyze(
} }
private fun condenseAnalysis( private fun condenseAnalysis(
repoRoot : File, repoRoot: File,
projectRoot : File, projectRoot: File,
fname : File, fname: File,
lineModel : LineModel, lineModel: LineModel,
knowledgeModel : KnowledgeModel, knowledgeModel: KnowledgeModel,
riskModel : RiskModel riskModel: RiskModel
) : CondensedAnalysis { ): CondensedAnalysis {
val condensations = lineModel.get().mapIndexed { idx, line -> val condensations = lineModel.get().mapIndexed { idx, line ->
val knowledges = knowledgeModel.knowledgeSummary(idx + 1).map { (authors, knowledge) -> val knowledges = knowledgeModel.knowledgeSummary(idx + 1).map { (authors, knowledge) ->
Condensation(authors, Condensation(authors,
knowledge, knowledge,
if(authors.all(riskModel::isDeparted)) knowledge else 0.0, if (authors.all(riskModel::isDeparted)) knowledge else 0.0,
riskModel.jointBusProb(authors) * knowledge) riskModel.jointBusProb(authors) * knowledge)
}.sorted() }.sorted()
Pair(line, knowledges) Pair(line, knowledges)
} }
return CondensedAnalysis(repoRoot, projectRoot, fname, condensations.mutableCopyOf()) return CondensedAnalysis(repoRoot, projectRoot, fname, condensations.mutableCopyOf())
} }

View File

@ -1,10 +1,10 @@
package me.msoucy.gbat package me.msoucy.gbat
import me.msoucy.gbat.models.RiskModel import com.xenomachina.argparser.ArgParser
import me.msoucy.gbat.models.SummaryModel import com.xenomachina.argparser.InvalidArgumentException
import com.xenomachina.argparser.default
import com.xenomachina.argparser.mainBody
import java.io.File import java.io.File
import java.util.concurrent.Executors
import kotlin.math.pow import kotlin.math.pow
import kotlin.system.exitProcess import kotlin.system.exitProcess
import kotlin.text.Regex import kotlin.text.Regex
@ -12,90 +12,85 @@ import kotlin.text.RegexOption
import kotlin.text.startsWith import kotlin.text.startsWith
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import me.msoucy.gbat.models.RiskModel
import com.xenomachina.argparser.ArgParser import me.msoucy.gbat.models.SummaryModel
import com.xenomachina.argparser.InvalidArgumentException
import com.xenomachina.argparser.default
import com.xenomachina.argparser.mainBody
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
val REALLY_LONG_TIME = 864000 val REALLY_LONG_TIME = 864000
val DEFAULT_INTERESTING_RES = mutableListOf( val DEFAULT_INTERESTING_RES = mutableListOf(
"\\.java$", "\\.java$",
"\\.cs$", "\\.cs$",
"\\.py$", "\\.py$",
"\\.c$", "\\.c$",
"\\.cpp$", "\\.cpp$",
"\\.h$", "\\.h$",
"\\.hpp$", "\\.hpp$",
"\\.pl$", "\\.pl$",
"\\.perl$", "\\.perl$",
"\\.rb$", "\\.rb$",
"\\.sh$", "\\.sh$",
"\\.js$", "\\.js$",
"\\.kt$" "\\.kt$"
) )
fun validateGit(exe : String) : String { fun validateGit(exe: String): String {
val os = System.getProperty("os.name") val os = System.getProperty("os.name")
var fullexe = if(os.startsWith("Windows") && !exe.endsWith(".exe")) { var fullexe = if (os.startsWith("Windows") && !exe.endsWith(".exe")) {
exe + ".exe" exe + ".exe"
} else exe } else exe
var file = File(fullexe) var file = File(fullexe)
if(file.canRead()) { if (file.canRead()) {
return file.absolutePath return file.absolutePath
} }
for(path in System.getenv("PATH").split(";")) { for (path in System.getenv("PATH").split(";")) {
file = File(path, fullexe) file = File(path, fullexe)
if(file.canRead()) { if (file.canRead()) {
return file.absolutePath return file.absolutePath
} }
} }
throw InvalidArgumentException("Provided git executable does not exist") throw InvalidArgumentException("Provided git executable does not exist")
} }
class GbatArgs(parser: ArgParser) { class GbatArgs(parser: ArgParser) {
// Input options // Input options
val interesting by parser.adding("--interesting", "-I", val interesting by parser.adding("--interesting", "-I",
help="Regular expression to determine which files should be included in calculations.") help = "Regular expression to determine which files should be included in calculations.")
val not_interesting by parser.adding("--not-interesting", "-N", val not_interesting by parser.adding("--not-interesting", "-N",
help="Regular expression to determine which files should not be included in calculations.") help = "Regular expression to determine which files should not be included in calculations.")
val case_sensitive by parser.flagging("Use case sensitive regexps when determining interesting files (default is case-insensitive)") val case_sensitive by parser.flagging("Use case sensitive regexps when determining interesting files (default is case-insensitive)")
val departed by parser.storing("--departed-file", "-D", help="File listing departed devs, one per line", transform=::File).default<File?>(null) val departed by parser.storing("--departed-file", "-D", help = "File listing departed devs, one per line", transform = ::File).default<File?>(null)
val risk_file by parser.storing("--bus-risk-file", help="File of dev=float lines (e.g. ejorgensen=0.4) with custom bus risks for devs", transform=::File).default<File?>(null) val risk_file by parser.storing("--bus-risk-file", help = "File of dev=float lines (e.g. ejorgensen=0.4) with custom bus risks for devs", transform = ::File).default<File?>(null)
val default_bus_risk by parser.storing("--default-bus-risk", help="Default risk that a dev will be hit by a bus in your analysis timeframe (defaults to 0.1).") { toDouble() }.default(0.1) val default_bus_risk by parser.storing("--default-bus-risk", help = "Default risk that a dev will be hit by a bus in your analysis timeframe (defaults to 0.1).") { toDouble() }.default(0.1)
// Multiprocessing options // Multiprocessing options
val num_git_procs by parser.storing("--num-git-procs", help="The number of git processes to run simultaneously (defaults to 3)") { toInt() }.default(3) val num_git_procs by parser.storing("--num-git-procs", help = "The number of git processes to run simultaneously (defaults to 3)") { toInt() }.default(3)
val num_analyzer_procs by parser.storing("--num-analyzer-procs", help="The number of analyzer processes to run (defaults to 3)") { toInt() }.default(3) val num_analyzer_procs by parser.storing("--num-analyzer-procs", help = "The number of analyzer processes to run (defaults to 3)") { toInt() }.default(3)
// Tuning options // Tuning options
val risk_threshold by parser.storing("--risk-threshold", help="Threshold past which to summarize risk (defaults to default bus risk cubed)") { toDouble() }.default<Double?>(null) val risk_threshold by parser.storing("--risk-threshold", help = "Threshold past which to summarize risk (defaults to default bus risk cubed)") { toDouble() }.default<Double?>(null)
val creation_constant by parser.storing("--knowledge-creation-constant", help="How much knowledge a changed line should create if a new line creates 1 (defaults to 0.1)") { toDouble() }.default(0.1) val creation_constant by parser.storing("--knowledge-creation-constant", help = "How much knowledge a changed line should create if a new line creates 1 (defaults to 0.1)") { toDouble() }.default(0.1)
// Misc options // Misc options
val git_exe by parser.storing("--git-exe", help="Path to the git executable", transform=::validateGit).default("git").addValidator { validateGit(value) } val git_exe by parser.storing("--git-exe", help = "Path to the git executable", transform = ::validateGit).default("git").addValidator { validateGit(value) }
val verbose by parser.flagging("--verbose", "-v", help="Print comforting output") val verbose by parser.flagging("--verbose", "-v", help = "Print comforting output")
val output by parser.storing("Output directory for data files and html summary (defaults to \"output\"), error if already exists").default("output") val output by parser.storing("Output directory for data files and html summary (defaults to \"output\"), error if already exists").default("output")
// Directory // Directory
val project_root by parser.positional("The root directory to inspect") val project_root by parser.positional("The root directory to inspect")
} }
fun main(args: Array<String>) = mainBody { fun main(args: Array<String>) = mainBody {
ArgParser(args).parseInto(::GbatArgs).run { ArgParser(args).parseInto(::GbatArgs).run {
val outDir = File(output) val outDir = File(output)
if(outDir.isDirectory) { if (outDir.isDirectory) {
//throw InvalidArgumentException("Output directory already exists") // throw InvalidArgumentException("Output directory already exists")
} }
outDir.mkdirs() outDir.mkdirs()
fun parse_interesting(theList : List<String>) = fun parse_interesting(theList: List<String>) =
theList.map { theList.map {
if(case_sensitive) { if (case_sensitive) {
Regex(it) Regex(it)
} else { } else {
Regex(it, RegexOption.IGNORE_CASE) Regex(it, RegexOption.IGNORE_CASE)
@ -107,21 +102,21 @@ fun main(args: Array<String>) = mainBody {
val not_interesting_res = if (not_interesting.isEmpty()) listOf() else parse_interesting(not_interesting) val not_interesting_res = if (not_interesting.isEmpty()) listOf() else parse_interesting(not_interesting)
val projectRootFile = File(project_root).also { val projectRootFile = File(project_root).also {
if(!it.isDirectory) if (!it.isDirectory)
throw InvalidArgumentException("Provided project root does not exist") throw InvalidArgumentException("Provided project root does not exist")
} }
val repo = GitRepo(projectRootFile, validateGit(git_exe)) val repo = GitRepo(projectRootFile, validateGit(git_exe))
fun String.isInteresting() : Boolean { fun String.isInteresting(): Boolean {
var hasInterest = interesting_res.any { it.containsMatchIn(this) } var hasInterest = interesting_res.any { it.containsMatchIn(this) }
if(hasInterest) { if (hasInterest) {
hasInterest = !not_interesting_res.any { it.containsMatchIn(this) } hasInterest = !not_interesting_res.any { it.containsMatchIn(this) }
} }
return hasInterest return hasInterest
} }
fun GitRepo.interestingNames() = ls().split("\n").filter{ it.isInteresting() } fun GitRepo.interestingNames() = ls().split("\n").filter { it.isInteresting() }
val fnames = repo.interestingNames() val fnames = repo.interestingNames()
if (fnames.isEmpty()) { if (fnames.isEmpty()) {
@ -136,13 +131,8 @@ fun main(args: Array<String>) = mainBody {
val riskModel = RiskModel(riskThresh, default_bus_risk, risk_file, departed) val riskModel = RiskModel(riskThresh, default_bus_risk, risk_file, departed)
val dbFname = File(outDir, "summary.db") val dbFname = File(outDir, "summary.db")
dbFname.delete(); dbFname.delete()
val summaryDb = Database.connect("jdbc:sqlite:${dbFname.absolutePath}", driver="org.sqlite.JDBC") val summaryDb = Database.connect("jdbc:sqlite:${dbFname.absolutePath}", driver = "org.sqlite.JDBC")
transaction(summaryDb) {
addLogger(StdOutSqlLogger)
// exec("PRAGMA journal_mode = OFF")
// exec("PRAGMA synchronous = OFF")
}
val summaryModel = SummaryModel(summaryDb) val summaryModel = SummaryModel(summaryDb)
runBlocking { runBlocking {
@ -160,6 +150,6 @@ fun main(args: Array<String>) = mainBody {
renderSummary(projectRootFile, summaryModel, outDir) renderSummary(projectRootFile, summaryModel, outDir)
// Render summary // Render summary
System.err.println("Done, summary is in ${outDir}/index.html") System.err.println("Done, summary is in $outDir/index.html")
} }
} }

View File

@ -3,25 +3,26 @@ package me.msoucy.gbat
import java.io.File import java.io.File
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import me.msoucy.gbat.models.ChangeType import me.msoucy.gbat.models.ChangeType
import me.msoucy.gbat.models.Event import me.msoucy.gbat.models.Event
data class HistoryItem( data class HistoryItem(
val repoRoot : File, val repoRoot: File,
val projectRoot : File, val projectRoot: File,
val fname : File, val fname: File,
val authorDiffs : List<Pair<String, List<Event>>> val authorDiffs: List<Pair<String, List<Event>>>
) )
fun parseHistory(repo : GitRepo, fun parseHistory(
projectRoot : File, repo: GitRepo,
fname : File, projectRoot: File,
verbose : Boolean = false) : HistoryItem { fname: File,
verbose: Boolean = false
): HistoryItem {
val entries = repo.log(fname) val entries = repo.log(fname)
val repoRoot = repo.root() val repoRoot = repo.root()
if(verbose) { if (verbose) {
System.err.println("Parsing history for ${fname}") System.err.println("Parsing history for $fname")
} }
return HistoryItem(repoRoot, projectRoot, fname, return HistoryItem(repoRoot, projectRoot, fname,
entries.map { (author, diff) -> entries.map { (author, diff) ->
@ -30,27 +31,27 @@ fun parseHistory(repo : GitRepo,
) )
} }
fun diffWalk(diff : Diff) : List<Event> { fun diffWalk(diff: Diff): List<Event> {
fun String.startsChunk() = startsWith("@@") fun String.startsChunk() = startsWith("@@")
fun String.isOldLine() = startsWith("-") fun String.isOldLine() = startsWith("-")
fun String.isNewLine() = startsWith("+") fun String.isNewLine() = startsWith("+")
fun chunkify() : List<List<String>> { fun chunkify(): List<List<String>> {
val chunks = mutableListOf<MutableList<String>>() val chunks = mutableListOf<MutableList<String>>()
var curChunk = mutableListOf<String>() var curChunk = mutableListOf<String>()
diff.split("\n").forEach { line -> diff.split("\n").forEach { line ->
if(line.startsChunk()) { if (line.startsChunk()) {
if(curChunk.isNotEmpty()) { if (curChunk.isNotEmpty()) {
chunks.add(curChunk) chunks.add(curChunk)
curChunk = mutableListOf<String>() curChunk = mutableListOf<String>()
} }
curChunk.add(line) curChunk.add(line)
} else if(curChunk.isNotEmpty()) { } else if (curChunk.isNotEmpty()) {
curChunk.add(line) curChunk.add(line)
} }
} }
if(curChunk.isNotEmpty()) { if (curChunk.isNotEmpty()) {
chunks.add(curChunk) chunks.add(curChunk)
} }
return chunks return chunks
@ -60,23 +61,23 @@ fun diffWalk(diff : Diff) : List<Event> {
val events = mutableListOf<Event>() val events = mutableListOf<Event>()
class Hunk( class Hunk(
val lineNum : Int, val lineNum: Int,
val oldLines : List<String>, val oldLines: List<String>,
val newLines : List<String> val newLines: List<String>
) )
fun hunkize(chunkWoHeader : List<String>, firstLineNum : Int) : List<Hunk> { fun hunkize(chunkWoHeader: List<String>, firstLineNum: Int): List<Hunk> {
var curOld = mutableListOf<String>() var curOld = mutableListOf<String>()
var curNew = mutableListOf<String>() var curNew = mutableListOf<String>()
var curLine = firstLineNum var curLine = firstLineNum
var hunks = mutableListOf<Hunk>() var hunks = mutableListOf<Hunk>()
chunkWoHeader.forEach { line -> chunkWoHeader.forEach { line ->
if(line.isOldLine()) { if (line.isOldLine()) {
curOld.add(line) curOld.add(line)
} else if(line.isNewLine()) { } else if (line.isNewLine()) {
curNew.add(line) curNew.add(line)
} else if(curOld.isNotEmpty() || curNew.isNotEmpty()) { } else if (curOld.isNotEmpty() || curNew.isNotEmpty()) {
hunks.add(Hunk(curLine, curOld, curNew)) hunks.add(Hunk(curLine, curOld, curNew))
curLine += curNew.size + 1 curLine += curNew.size + 1
curOld = mutableListOf<String>() curOld = mutableListOf<String>()
@ -85,28 +86,28 @@ fun diffWalk(diff : Diff) : List<Event> {
curLine++ curLine++
} }
} }
if(curOld.isNotEmpty() || curNew.isNotEmpty()) { if (curOld.isNotEmpty() || curNew.isNotEmpty()) {
hunks.add(Hunk(curLine, curOld, curNew)) hunks.add(Hunk(curLine, curOld, curNew))
} }
return hunks return hunks
} }
fun stepHunk(hunk : Hunk) { fun stepHunk(hunk: Hunk) {
val oldLen = hunk.oldLines.size val oldLen = hunk.oldLines.size
val newLen = hunk.newLines.size val newLen = hunk.newLines.size
val maxLen = max(oldLen, newLen) val maxLen = max(oldLen, newLen)
var lineNum = hunk.lineNum var lineNum = hunk.lineNum
for (i in 0 until maxLen) { for (i in 0 until maxLen) {
if(i < oldLen && i < newLen) { if (i < oldLen && i < newLen) {
events += Event( events += Event(
ChangeType.Change, ChangeType.Change,
lineNum, lineNum,
hunk.newLines[i].substring(1) hunk.newLines[i].substring(1)
) )
lineNum++ lineNum++
} else if(i < oldLen) { } else if (i < oldLen) {
events += Event( events += Event(
ChangeType.Remove, ChangeType.Remove,
lineNum, lineNum,
@ -123,7 +124,7 @@ fun diffWalk(diff : Diff) : List<Event> {
} }
} }
fun stepChunk(chunk : List<String>) { fun stepChunk(chunk: List<String>) {
val header = chunk[0] val header = chunk[0]
// format of header is // format of header is
@ -137,7 +138,7 @@ fun diffWalk(diff : Diff) : List<Event> {
// of the file the new and old are the same, and since we add // of the file the new and old are the same, and since we add
// and subtract lines as we go, we should stay in step with the // and subtract lines as we go, we should stay in step with the
// new offsets. // new offsets.
val newOffset = offsets[1].split(",").map{ val newOffset = offsets[1].split(",").map {
abs(it.toInt()) abs(it.toInt())
}.first() }.first()
@ -150,4 +151,4 @@ fun diffWalk(diff : Diff) : List<Event> {
chunks.forEach(::stepChunk) chunks.forEach(::stepChunk)
return events return events
} }

View File

@ -5,8 +5,8 @@ import java.io.IOException
typealias Diff = String typealias Diff = String
class GitRepo(val projectRoot : File, val git_exe : String) { class GitRepo(val projectRoot: File, val git_exe: String) {
fun ls() : String { fun ls(): String {
val cmd = listOf( val cmd = listOf(
git_exe, git_exe,
"ls-tree", "ls-tree",
@ -20,7 +20,7 @@ class GitRepo(val projectRoot : File, val git_exe : String) {
return out ?: "" return out ?: ""
} }
fun root() : File { fun root(): File {
val cmd = listOf( val cmd = listOf(
git_exe, git_exe,
"rev-parse", "rev-parse",
@ -30,7 +30,7 @@ class GitRepo(val projectRoot : File, val git_exe : String) {
return File((out ?: "").trim()) return File((out ?: "").trim())
} }
fun log(fname : File) : List<Pair<String, Diff>> { fun log(fname: File): List<Pair<String, Diff>> {
val cmd = listOf( val cmd = listOf(
git_exe, git_exe,
"--no-pager", "--no-pager",
@ -43,11 +43,11 @@ class GitRepo(val projectRoot : File, val git_exe : String) {
fname.absolutePath fname.absolutePath
) )
val (out, err) = cmd.runCommand(projectRoot) val (out, err) = cmd.runCommand(projectRoot)
if(err != "") { if (err != "") {
System.err.println("Error from git log: " + err) System.err.println("Error from git log: " + err)
throw IOException(err) throw IOException(err)
} }
val logEntries = (out?: "").split("\u0000").filter {it.trim().isNotEmpty()} val logEntries = (out ?: "").split("\u0000").filter { it.trim().isNotEmpty() }
return logEntries.map { return logEntries.map {
val (header, diffLines) = splitEntryHeader(it) val (header, diffLines) = splitEntryHeader(it)
val diff = diffLines.joinToString("\n") val diff = diffLines.joinToString("\n")
@ -58,25 +58,25 @@ class GitRepo(val projectRoot : File, val git_exe : String) {
}.reversed() }.reversed()
} }
private fun parseAuthor(header : List<String>) : String { private fun parseAuthor(header: List<String>): String {
val segs = header.getOrNull(1)?.trim()?.split("\\s+".toRegex())?: listOf() val segs = header.getOrNull(1)?.trim()?.split("\\s+".toRegex()) ?: listOf()
return segs.subList(1, segs.size - 1).joinToString(" ") return segs.subList(1, segs.size - 1).joinToString(" ")
} }
private fun splitEntryHeader(entry : String) : Pair<List<String>, List<String>> { private fun splitEntryHeader(entry: String): Pair<List<String>, List<String>> {
val lines = entry.split("\n") val lines = entry.split("\n")
if(lines.size < 2) { if (lines.size < 2) {
return Pair(listOf(), listOf()) return Pair(listOf(), listOf())
} else if(!lines.get(0).startsWith("commit")) { } else if (!lines.get(0).startsWith("commit")) {
return Pair(listOf(), listOf()) return Pair(listOf(), listOf())
} else if(!lines.get(1).startsWith("Author")) { } else if (!lines.get(1).startsWith("Author")) {
return Pair(listOf(), listOf()) return Pair(listOf(), listOf())
} }
var ind = 2 var ind = 2
while(ind < lines.size && !lines.get(ind).startsWith("diff")) { while (ind < lines.size && !lines.get(ind).startsWith("diff")) {
ind++ ind++
} }
return Pair(lines.subList(0, ind).copyOf(), return Pair(lines.subList(0, ind).copyOf(),
lines.subList(ind, lines.size).copyOf()) lines.subList(ind, lines.size).copyOf())
} }
} }

View File

@ -4,10 +4,10 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
fun <T> Iterable<T>.copyOf() : List<T> = mutableListOf<T>().also { it.addAll(this) } fun <T> Iterable<T>.copyOf(): List<T> = mutableListOf<T>().also { it.addAll(this) }
fun <T> Iterable<T>.mutableCopyOf() = mutableListOf<T>().also { it.addAll(this) } fun <T> Iterable<T>.mutableCopyOf() = mutableListOf<T>().also { it.addAll(this) }
fun List<String>.runCommand(workingDir: File): Pair<String?,String?> { fun List<String>.runCommand(workingDir: File): Pair<String?, String?> {
try { try {
val proc = ProcessBuilder(*this.toTypedArray()) val proc = ProcessBuilder(*this.toTypedArray())
.directory(workingDir) .directory(workingDir)
@ -18,8 +18,8 @@ fun List<String>.runCommand(workingDir: File): Pair<String?,String?> {
proc.waitFor(5, TimeUnit.SECONDS) proc.waitFor(5, TimeUnit.SECONDS)
return Pair(proc.inputStream.bufferedReader().readText(), return Pair(proc.inputStream.bufferedReader().readText(),
proc.errorStream.bufferedReader().readText()) proc.errorStream.bufferedReader().readText())
} catch(e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
return Pair(null, null) return Pair(null, null)
} }
} }

View File

@ -1,20 +1,18 @@
package me.msoucy.gbat.models package me.msoucy.gbat.models
import java.io.File import me.msoucy.gbat.copyOf
import java.nio.file.Path import me.msoucy.gbat.mutableCopyOf
import java.nio.file.Paths
import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import me.msoucy.gbat.copyOf class KnowledgeModel(val db: Database, val constant: Double, val riskModel: RiskModel) {
import me.msoucy.gbat.mutableCopyOf
class KnowledgeModel(val db : Database, val constant : Double, val riskModel : RiskModel) { class KnowledgeAcct(
var knowledgeAcctId: Int,
class KnowledgeAcct(var knowledgeAcctId : Int, var authors: List<String>,
var authors : List<String>, var authorsStr: String
var authorsStr : String) )
object AuthorsTable : IntIdTable("authors", "authorid") { object AuthorsTable : IntIdTable("authors", "authorid") {
val author = text("author").uniqueIndex("authors_idx") val author = text("author").uniqueIndex("authors_idx")
@ -41,13 +39,13 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
val SAFE_KNOWLEDGE_ACCT_ID = 1 val SAFE_KNOWLEDGE_ACCT_ID = 1
val KNOWLEDGE_PER_LINE_ADDED = 1000.0 val KNOWLEDGE_PER_LINE_ADDED = 1000.0
fun apply(changeType : ChangeType, author : String, lineNum : Int) = when(changeType) { fun apply(changeType: ChangeType, author: String, lineNum: Int) = when (changeType) {
ChangeType.Add -> lineAdded(author, lineNum) ChangeType.Add -> lineAdded(author, lineNum)
ChangeType.Change -> lineChanged(author, lineNum) ChangeType.Change -> lineChanged(author, lineNum)
ChangeType.Remove -> lineRemoved(lineNum) ChangeType.Remove -> lineRemoved(lineNum)
} }
fun lineChanged(author : String, lineNum : Int) { fun lineChanged(author: String, lineNum: Int) {
val kCreated = constant * KNOWLEDGE_PER_LINE_ADDED val kCreated = constant * KNOWLEDGE_PER_LINE_ADDED
val kAcquired = (1 - constant) * KNOWLEDGE_PER_LINE_ADDED val kAcquired = (1 - constant) * KNOWLEDGE_PER_LINE_ADDED
val totLineK = totalLineKnowledge(lineNum) val totLineK = totalLineKnowledge(lineNum)
@ -59,20 +57,20 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
adjustKnowledge(knowledgeAcctId, lineNum, kCreated) adjustKnowledge(knowledgeAcctId, lineNum, kCreated)
} }
fun lineRemoved(lineNum : Int) { fun lineRemoved(lineNum: Int) {
allAcctsWithKnowledgeOf(lineNum).forEach { allAcctsWithKnowledgeOf(lineNum).forEach {
destroyLineKnowledge(it, lineNum) destroyLineKnowledge(it, lineNum)
} }
bumpAllLinesFrom(lineNum, -1) bumpAllLinesFrom(lineNum, -1)
} }
fun lineAdded(author : String, lineNum : Int) { fun lineAdded(author: String, lineNum: Int) {
val knowledgeAcctId = lookupOrCreateKnowledgeAcct(listOf(author)) val knowledgeAcctId = lookupOrCreateKnowledgeAcct(listOf(author))
bumpAllLinesFrom(lineNum-1, 1) bumpAllLinesFrom(lineNum - 1, 1)
adjustKnowledge(knowledgeAcctId, lineNum, KNOWLEDGE_PER_LINE_ADDED) adjustKnowledge(knowledgeAcctId, lineNum, KNOWLEDGE_PER_LINE_ADDED)
} }
fun knowledgeSummary(lineNum : Int) = transaction(db) { fun knowledgeSummary(lineNum: Int) = transaction(db) {
LineKnowledge.select { LineKnowledge.select {
LineKnowledge.linenum eq lineNum LineKnowledge.linenum eq lineNum
}.map { }.map {
@ -83,15 +81,15 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
}.copyOf() }.copyOf()
} }
private fun bumpAllLinesFrom(lineNum : Int, adjustment : Int) = transaction(db) { private fun bumpAllLinesFrom(lineNum: Int, adjustment: Int) = transaction(db) {
LineKnowledge.update({LineKnowledge.linenum greater lineNum}) { LineKnowledge.update({ LineKnowledge.linenum greater lineNum }) {
with(SqlExpressionBuilder) { with(SqlExpressionBuilder) {
it[LineKnowledge.linenum] = LineKnowledge.linenum + adjustment it[LineKnowledge.linenum] = LineKnowledge.linenum + adjustment
} }
} }
} }
private fun getKnowledgeAcct(knowledgeAcctId : Int) : KnowledgeAcct = transaction(db) { private fun getKnowledgeAcct(knowledgeAcctId: Int): KnowledgeAcct = transaction(db) {
KnowledgeAcctsTable.select { KnowledgeAcctsTable.select {
KnowledgeAcctsTable.id eq knowledgeAcctId KnowledgeAcctsTable.id eq knowledgeAcctId
}.map { }.map {
@ -103,15 +101,15 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
}.firstOrNull() ?: KnowledgeAcct(-1, listOf(), "") }.firstOrNull() ?: KnowledgeAcct(-1, listOf(), "")
} }
private fun destroyLineKnowledge(knowledgeId : Int, lineNum : Int) = transaction(db) { private fun destroyLineKnowledge(knowledgeId: Int, lineNum: Int) = transaction(db) {
LineKnowledge.deleteWhere { LineKnowledge.deleteWhere {
(LineKnowledge.knowledgeacctid eq knowledgeId) and (LineKnowledge.knowledgeacctid eq knowledgeId) and
(LineKnowledge.linenum eq lineNum) (LineKnowledge.linenum eq lineNum)
} }
} }
private fun redistributeKnowledge(author : String, lineNum : Int, redistPct : Double) { private fun redistributeKnowledge(author: String, lineNum: Int, redistPct: Double) {
if(riskModel.isDeparted(author)) { if (riskModel.isDeparted(author)) {
return return
} }
val knowledgeIds = nonSafeAcctsWithKnowledgeOf(lineNum) val knowledgeIds = nonSafeAcctsWithKnowledgeOf(lineNum)
@ -120,13 +118,13 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
if (author !in knowledgeAcct.authors) { if (author !in knowledgeAcct.authors) {
val oldKnowledge = knowledgeInAcct(knowledgeAcct.knowledgeAcctId, lineNum) val oldKnowledge = knowledgeInAcct(knowledgeAcct.knowledgeAcctId, lineNum)
var newAuthors = knowledgeAcct.authors.mutableCopyOf() var newAuthors = knowledgeAcct.authors.mutableCopyOf()
if(newAuthors.all(riskModel::isDeparted)) { if (newAuthors.all(riskModel::isDeparted)) {
newAuthors = mutableListOf(author) newAuthors = mutableListOf(author)
} else { } else {
newAuthors.add(author) newAuthors.add(author)
} }
newAuthors = newAuthors.sorted().mutableCopyOf() newAuthors = newAuthors.sorted().mutableCopyOf()
val newKnowledgeId = if(riskModel.jointBusProbBelowThreshold(newAuthors)) { val newKnowledgeId = if (riskModel.jointBusProbBelowThreshold(newAuthors)) {
SAFE_KNOWLEDGE_ACCT_ID SAFE_KNOWLEDGE_ACCT_ID
} else { } else {
lookupOrCreateKnowledgeAcct(newAuthors) lookupOrCreateKnowledgeAcct(newAuthors)
@ -138,7 +136,7 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
} }
} }
private fun knowledgeInAcct(knowledgeAcctId : Int, lineNum : Int) = transaction(db) { private fun knowledgeInAcct(knowledgeAcctId: Int, lineNum: Int) = transaction(db) {
LineKnowledge.select { LineKnowledge.select {
(LineKnowledge.knowledgeacctid eq knowledgeAcctId) and (LineKnowledge.knowledgeacctid eq knowledgeAcctId) and
(LineKnowledge.linenum eq lineNum) (LineKnowledge.linenum eq lineNum)
@ -147,7 +145,7 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
}.first() }.first()
} }
private fun nonSafeAcctsWithKnowledgeOf(lineNum : Int) = transaction(db) { private fun nonSafeAcctsWithKnowledgeOf(lineNum: Int) = transaction(db) {
LineKnowledge.select { LineKnowledge.select {
(LineKnowledge.linenum eq lineNum) and (LineKnowledge.linenum eq lineNum) and
(LineKnowledge.knowledgeacctid neq SAFE_KNOWLEDGE_ACCT_ID) (LineKnowledge.knowledgeacctid neq SAFE_KNOWLEDGE_ACCT_ID)
@ -156,7 +154,7 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
} }
} }
private fun allAcctsWithKnowledgeOf(lineNum : Int) = transaction(db) { private fun allAcctsWithKnowledgeOf(lineNum: Int) = transaction(db) {
LineKnowledge.select { LineKnowledge.select {
LineKnowledge.linenum eq lineNum LineKnowledge.linenum eq lineNum
}.map { }.map {
@ -164,12 +162,12 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
} }
} }
private fun adjustKnowledge(knowledgeAcctId : Int, lineNum : Int, adjustment : Double) = transaction(db) { private fun adjustKnowledge(knowledgeAcctId: Int, lineNum: Int, adjustment: Double) = transaction(db) {
val lineExists = LineKnowledge.select { val lineExists = LineKnowledge.select {
(LineKnowledge.knowledgeacctid eq knowledgeAcctId) and (LineKnowledge.knowledgeacctid eq knowledgeAcctId) and
(LineKnowledge.linenum eq lineNum) (LineKnowledge.linenum eq lineNum)
}.count() > 0 }.count() > 0
if(!lineExists) { if (!lineExists) {
LineKnowledge.insert { LineKnowledge.insert {
it[LineKnowledge.knowledgeacctid] = knowledgeAcctId it[LineKnowledge.knowledgeacctid] = knowledgeAcctId
it[LineKnowledge.linenum] = lineNum it[LineKnowledge.linenum] = lineNum
@ -179,21 +177,21 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
LineKnowledge.update({ LineKnowledge.update({
(LineKnowledge.knowledgeacctid eq knowledgeAcctId) and (LineKnowledge.knowledgeacctid eq knowledgeAcctId) and
(LineKnowledge.linenum eq lineNum) (LineKnowledge.linenum eq lineNum)
}) { }) {
with(SqlExpressionBuilder) { with(SqlExpressionBuilder) {
it[LineKnowledge.knowledge] = LineKnowledge.knowledge + adjustment it[LineKnowledge.knowledge] = LineKnowledge.knowledge + adjustment
} }
} }
} }
private fun lookupOrCreateKnowledgeAcct(authors : List<String>) = transaction(db) { private fun lookupOrCreateKnowledgeAcct(authors: List<String>) = transaction(db) {
val authorStr = authors.sorted().joinToString("\n") val authorStr = authors.sorted().joinToString("\n")
KnowledgeAcctsTable.select { KnowledgeAcctsTable.select {
KnowledgeAcctsTable.authors eq authorStr KnowledgeAcctsTable.authors eq authorStr
}.map { }.map {
it[KnowledgeAcctsTable.id].value it[KnowledgeAcctsTable.id].value
}.firstOrNull() ?: run { }.firstOrNull() ?: run {
KnowledgeAcctsTable.insert { KnowledgeAcctsTable.insert {
it[KnowledgeAcctsTable.authors] = authorStr it[KnowledgeAcctsTable.authors] = authorStr
} }
val theNewId = KnowledgeAcctsTable.select { val theNewId = KnowledgeAcctsTable.select {
@ -212,8 +210,8 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
} }
} }
private fun lookupOrCreateAuthor(authorName : String) = transaction(db) { private fun lookupOrCreateAuthor(authorName: String) = transaction(db) {
AuthorsTable.insertIgnore { AuthorsTable.insertIgnore {
it[author] = authorName it[author] = authorName
} }
AuthorsTable.select { AuthorsTable.select {
@ -222,8 +220,8 @@ class KnowledgeModel(val db : Database, val constant : Double, val riskModel : R
it[AuthorsTable.id] it[AuthorsTable.id]
} }
} }
private fun totalLineKnowledge(linenum : Int) = transaction(db) { private fun totalLineKnowledge(linenum: Int) = transaction(db) {
LineKnowledge.select { LineKnowledge.select {
LineKnowledge.linenum eq linenum LineKnowledge.linenum eq linenum
}.map { }.map {

View File

@ -1,42 +1,34 @@
package me.msoucy.gbat.models package me.msoucy.gbat.models
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.forEachLine
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
class LineModel() { class LineModel() {
inner class Line(var num : Int, var text : String) inner class Line(var num: Int, var text: String)
val model = mutableSetOf<Line>() val model = mutableSetOf<Line>()
fun apply(changeType : ChangeType, lineNum : Int, lineText : String) = when(changeType) { fun apply(changeType: ChangeType, lineNum: Int, lineText: String) = when (changeType) {
ChangeType.Add -> add(Line(lineNum, lineText)) ChangeType.Add -> add(Line(lineNum, lineText))
ChangeType.Change -> change(Line(lineNum, lineText)) ChangeType.Change -> change(Line(lineNum, lineText))
ChangeType.Remove -> del(Line(lineNum, lineText)) ChangeType.Remove -> del(Line(lineNum, lineText))
} }
fun add(line : Line) { fun add(line: Line) {
model.onEach { entry -> model.onEach { entry ->
if(entry.num >= line.num) { if (entry.num >= line.num) {
entry.num++ entry.num++
} }
} }
model.add(line) model.add(line)
} }
fun del(line : Line) { fun del(line: Line) {
model.removeIf { it.num == line.num } model.removeIf { it.num == line.num }
model.onEach { entry -> model.onEach { entry ->
if(entry.num > line.num) { if (entry.num > line.num) {
entry.num-- entry.num--
} }
} }
} }
fun change(line : Line) { fun change(line: Line) {
model.removeIf { it.num == line.num } model.removeIf { it.num == line.num }
model.add(line) model.add(line)
} }

View File

@ -7,37 +7,37 @@ enum class ChangeType {
} }
data class Event( data class Event(
val eventType : ChangeType, val eventType: ChangeType,
val lineNum : Int, val lineNum: Int,
val lineVal : String? val lineVal: String?
) )
data class Condensation( data class Condensation(
val authors : List<String>, val authors: List<String>,
val knowledge : Double, val knowledge: Double,
val orphaned : Double, val orphaned: Double,
val risk : Double = 0.0 val risk: Double = 0.0
) : Comparable<Condensation> { ) : Comparable<Condensation> {
override operator fun compareTo(other : Condensation) : Int { override operator fun compareTo(other: Condensation): Int {
var result = authors.size.compareTo(other.authors.size) var result = authors.size.compareTo(other.authors.size)
if(result == 0) { if (result == 0) {
authors.zip(other.authors).forEach { (a, b) -> authors.zip(other.authors).forEach { (a, b) ->
if(result == 0) result = a.compareTo(b) if (result == 0) result = a.compareTo(b)
} }
} }
if(result == 0) if (result == 0)
result = knowledge.compareTo(other.knowledge) result = knowledge.compareTo(other.knowledge)
if(result == 0) if (result == 0)
result = orphaned.compareTo(other.orphaned) result = orphaned.compareTo(other.orphaned)
if(result == 0) if (result == 0)
result = risk.compareTo(other.risk) result = risk.compareTo(other.risk)
return result return result
} }
} }
class CondensedAnalysis( class CondensedAnalysis(
var repoRoot : File, var repoRoot: File,
var projectRoot : File, var projectRoot: File,
var fileName : File, var fileName: File,
var lineSummaries : MutableList<Pair<String, List<Condensation>>> = mutableListOf() var lineSummaries: MutableList<Pair<String, List<Condensation>>> = mutableListOf()
) )

View File

@ -3,50 +3,52 @@ package me.msoucy.gbat.models
import java.io.File import java.io.File
import kotlin.io.forEachLine import kotlin.io.forEachLine
class RiskModel(val threshold : Double, class RiskModel(
val default : Double, val threshold: Double,
val busRiskFile : File?, val default: Double,
val departedFile : File?) { val busRiskFile: File?,
val departedFile: File?
) {
val departed = mutableSetOf<String>() val departed = mutableSetOf<String>()
val risks = mutableMapOf<String, Double>().withDefault {default} val risks = mutableMapOf<String, Double>().withDefault { default }
init { init {
parseBusRisks() parseBusRisks()
parseDeparted() parseDeparted()
} }
operator fun get(author : String) : Double { operator fun get(author: String): Double {
val name = author.trim() val name = author.trim()
if(name.isEmpty()) { if (name.isEmpty()) {
return threshold return threshold
} }
return risks.getOrPut(name) { default } return risks.getOrPut(name) { default }
} }
fun isDeparted(author : String) = author.trim() in departed fun isDeparted(author: String) = author.trim() in departed
fun jointBusProb(authors : List<String>) = fun jointBusProb(authors: List<String>) =
(authors.map { this[it] } + 1.0).reduce { a, b -> a * b } (authors.map { this[it] } + 1.0).reduce { a, b -> a * b }
fun jointBusProbBelowThreshold(authors : List<String>) = fun jointBusProbBelowThreshold(authors: List<String>) =
jointBusProb(authors) <= threshold jointBusProb(authors) <= threshold
private fun parseBusRisks() { private fun parseBusRisks() {
busRiskFile?.forEachLine { line -> busRiskFile?.forEachLine { line ->
val sline = line.trim() val sline = line.trim()
if(sline.isNotEmpty()) { if (sline.isNotEmpty()) {
val segments = sline.split("=") val segments = sline.split("=")
val risk = segments.last() val risk = segments.last()
val author = segments.dropLast(1).joinToString(separator="=") val author = segments.dropLast(1).joinToString(separator = "=")
risks[author] = risk.toDouble() risks[author] = risk.toDouble()
} }
} }
} }
private fun parseDeparted() { private fun parseDeparted() {
departedFile?.forEachLine { line -> departedFile?.forEachLine { line ->
val author = line.trim() val author = line.trim()
if(author.isNotEmpty()) { if (author.isNotEmpty()) {
risks[author] = 1.0 risks[author] = 1.0
departed.add(author) departed.add(author)
} }

View File

@ -55,7 +55,7 @@ class FileTree {
var authorRisks = mutableMapOf<String, Statistics>() var authorRisks = mutableMapOf<String, Statistics>()
var lines = mutableListOf<LineDict>() var lines = mutableListOf<LineDict>()
} }
class FileEntry(var name : String = "") { class FileEntry(var name: String = "") {
var stats = Statistics() var stats = Statistics()
var authorRisks = mutableMapOf<String, Statistics>() var authorRisks = mutableMapOf<String, Statistics>()
} }
@ -64,14 +64,14 @@ class ProjectTree {
var files = mutableMapOf<Int, FileEntry>() var files = mutableMapOf<Int, FileEntry>()
var dirs = mutableListOf<Int>() var dirs = mutableListOf<Int>()
} }
class ProjectFilesResult(var fileId : Int, var fname : Path) class ProjectFilesResult(var fileId: Int, var fname: Path)
data class Statistics( data class Statistics(
var totKnowledge : Double = 0.0, var totKnowledge: Double = 0.0,
var totRisk : Double = 0.0, var totRisk: Double = 0.0,
var totOrphaned : Double = 0.0 var totOrphaned: Double = 0.0
) { ) {
constructor(row : ResultRow) : constructor(row: ResultRow) :
this(row[AllocationsTable.knowledge.sum()] ?: 0.0, this(row[AllocationsTable.knowledge.sum()] ?: 0.0,
row[AllocationsTable.risk.sum()] ?: 0.0, row[AllocationsTable.risk.sum()] ?: 0.0,
row[AllocationsTable.orphaned.sum()] ?: 0.0) {} row[AllocationsTable.orphaned.sum()] ?: 0.0) {}
@ -81,25 +81,25 @@ class ProjectTreeNode {
var files = mutableListOf<FileEntry>() var files = mutableListOf<FileEntry>()
var dirs = mutableListOf<ProjectTreeNode>() var dirs = mutableListOf<ProjectTreeNode>()
} }
class ProjectTreeResult(var name : String, var root : ProjectTreeNode) { class ProjectTreeResult(var name: String, var root: ProjectTreeNode) {
var stats = Statistics() var stats = Statistics()
var authorRisks = mutableMapOf<String, Statistics>() var authorRisks = mutableMapOf<String, Statistics>()
} }
class SummaryModel(val db : Database) { class SummaryModel(val db: Database) {
val GIT_BY_A_BUS_BELOW_THRESHOLD = "Git by a Bus Safe Author" val GIT_BY_A_BUS_BELOW_THRESHOLD = "Git by a Bus Safe Author"
init { init {
createTables() createTables()
} }
private val lineAllocations = (LinesTable leftJoin AllocationsTable) private val lineAllocations = (LinesTable leftJoin AllocationsTable)
private val lineAllocationGroups = (lineAllocations leftJoin AuthorsGroupsTable) private val lineAllocationGroups = (lineAllocations leftJoin AuthorsGroupsTable)
private val manyJoined = (lineAllocations leftJoin FilesTable leftJoin DirsTable) private val manyJoined = (lineAllocations leftJoin FilesTable leftJoin DirsTable)
private val allJoined = (manyJoined leftJoin AuthorsGroupsTable) private val allJoined = (manyJoined leftJoin AuthorsGroupsTable)
fun summarize(ca : CondensedAnalysis) { fun summarize(ca: CondensedAnalysis) {
val fname = adjustFname(ca.repoRoot.absoluteFile, val fname = adjustFname(ca.repoRoot.absoluteFile,
ca.projectRoot.absoluteFile, ca.projectRoot.absoluteFile,
ca.fileName.absoluteFile) ca.fileName.absoluteFile)
@ -122,7 +122,7 @@ class SummaryModel(val db : Database) {
} }
} }
} }
fun totalKnowledge() = transaction(db) { fun totalKnowledge() = transaction(db) {
AllocationsTable.selectAll().map { it[AllocationsTable.knowledge] }.sum() AllocationsTable.selectAll().map { it[AllocationsTable.knowledge] }.sum()
} }
@ -139,12 +139,12 @@ class SummaryModel(val db : Database) {
FilesTable.selectAll().count() FilesTable.selectAll().count()
} }
fun authorgroupsWithRisk(top : Int? = null) : List<Pair<String, Double>> = transaction(db) { fun authorgroupsWithRisk(top: Int? = null): List<Pair<String, Double>> = transaction(db) {
var query = (AllocationsTable innerJoin AuthorsGroupsTable) var query = (AllocationsTable innerJoin AuthorsGroupsTable)
.selectAll() .selectAll()
.groupBy(AuthorsGroupsTable.authors) .groupBy(AuthorsGroupsTable.authors)
.orderBy(AllocationsTable.risk.sum() to SortOrder.DESC) .orderBy(AllocationsTable.risk.sum() to SortOrder.DESC)
if(top != null) { if (top != null) {
query = query.limit(top) query = query.limit(top)
} }
query.map { query.map {
@ -152,12 +152,12 @@ class SummaryModel(val db : Database) {
} }
} }
fun fileidsWithRisk(top : Int? = null) : List<Pair<Int, Double>> = transaction(db) { fun fileidsWithRisk(top: Int? = null): List<Pair<Int, Double>> = transaction(db) {
var query = (FilesTable leftJoin LinesTable leftJoin AllocationsTable) var query = (FilesTable leftJoin LinesTable leftJoin AllocationsTable)
.selectAll() .selectAll()
.groupBy(FilesTable.id) .groupBy(FilesTable.id)
.orderBy(AllocationsTable.risk.sum() to SortOrder.DESC) .orderBy(AllocationsTable.risk.sum() to SortOrder.DESC)
if(top != null) { if (top != null) {
query = query.limit(top) query = query.limit(top)
} }
query.map { query.map {
@ -165,7 +165,7 @@ class SummaryModel(val db : Database) {
} }
} }
fun fpath(fileId : Int) : Path = transaction(db) { fun fpath(fileId: Int): Path = transaction(db) {
FilesTable.select { FilesTable.select {
FilesTable.id eq fileId FilesTable.id eq fileId
}.first().let { row -> }.first().let { row ->
@ -174,7 +174,7 @@ class SummaryModel(val db : Database) {
} }
} }
fun projectFiles(project : String) : List<ProjectFilesResult> = transaction(db) { fun projectFiles(project: String): List<ProjectFilesResult> = transaction(db) {
val projectId = findOrCreateProject(project) val projectId = findOrCreateProject(project)
(FilesTable innerJoin DirsTable).select { (FilesTable innerJoin DirsTable).select {
(FilesTable.dirid eq DirsTable.id) and (FilesTable.dirid eq DirsTable.id) and
@ -184,13 +184,13 @@ class SummaryModel(val db : Database) {
} }
} }
fun projectSummary(project : String) = transaction(db) { fun projectSummary(project: String) = transaction(db) {
val projectId = findOrCreateProject(project) val projectId = findOrCreateProject(project)
val theTree = mutableMapOf<Int, ProjectTree>().withDefault {ProjectTree()} val theTree = mutableMapOf<Int, ProjectTree>().withDefault { ProjectTree() }
// First fill in the directory structure, ignoring the files // First fill in the directory structure, ignoring the files
val parentDirIds = mutableListOf(0) val parentDirIds = mutableListOf(0)
while(parentDirIds.isNotEmpty()) { while (parentDirIds.isNotEmpty()) {
val parentId = parentDirIds.removeAt(0) val parentId = parentDirIds.removeAt(0)
DirsTable.select { DirsTable.parentdirid eq parentId } DirsTable.select { DirsTable.parentdirid eq parentId }
.forEach { row -> .forEach { row ->
@ -265,7 +265,7 @@ class SummaryModel(val db : Database) {
projectTree projectTree
} }
fun fileSummary(fileId : Int) = transaction(db) { fun fileSummary(fileId: Int) = transaction(db) {
var fileTree = FileTree() var fileTree = FileTree()
lineAllocationGroups lineAllocationGroups
.slice(AllocationsTable.knowledge.sum(), AllocationsTable.risk.sum(), AllocationsTable.orphaned.sum(), AuthorsGroupsTable.authors) .slice(AllocationsTable.knowledge.sum(), AllocationsTable.risk.sum(), AllocationsTable.orphaned.sum(), AuthorsGroupsTable.authors)
@ -311,7 +311,7 @@ class SummaryModel(val db : Database) {
fileTree fileTree
} }
fun fileLines(fileId : Int) : List<String> = transaction(db) { fun fileLines(fileId: Int): List<String> = transaction(db) {
LinesTable.select { LinesTable.select {
LinesTable.fileid eq fileId LinesTable.fileid eq fileId
}.orderBy(LinesTable.linenum).map { }.orderBy(LinesTable.linenum).map {
@ -319,7 +319,7 @@ class SummaryModel(val db : Database) {
} }
} }
private fun transformNode(tree : MutableMap<Int, ProjectTree>, dirId : Int) : ProjectTreeNode { private fun transformNode(tree: MutableMap<Int, ProjectTree>, dirId: Int): ProjectTreeNode {
val result = ProjectTreeNode() val result = ProjectTreeNode()
tree[dirId]?.let { dirdict -> tree[dirId]?.let { dirdict ->
result.name = dirdict.name result.name = dirdict.name
@ -334,10 +334,10 @@ class SummaryModel(val db : Database) {
return result return result
} }
private fun reconsDir(dirId : Int) = transaction(db) { private fun reconsDir(dirId: Int) = transaction(db) {
val segs = mutableListOf<String>() val segs = mutableListOf<String>()
var newDirId = dirId var newDirId = dirId
while(newDirId != 0) { while (newDirId != 0) {
DirsTable.select { DirsTable.select {
DirsTable.id eq newDirId DirsTable.id eq newDirId
}.forEach { }.forEach {
@ -348,9 +348,9 @@ class SummaryModel(val db : Database) {
Paths.get(segs.reversed().joinToString("/")).normalize() Paths.get(segs.reversed().joinToString("/")).normalize()
} }
private fun safeAuthorName(author : String?) = author ?: GIT_BY_A_BUS_BELOW_THRESHOLD private fun safeAuthorName(author: String?) = author ?: GIT_BY_A_BUS_BELOW_THRESHOLD
private fun createAllocation(knowledge : Double, risk : Double, orphaned : Double, authorGroupId : Int, lineId : Int) = transaction(db) { private fun createAllocation(knowledge: Double, risk: Double, orphaned: Double, authorGroupId: Int, lineId: Int) = transaction(db) {
AllocationsTable.insert { AllocationsTable.insert {
it[AllocationsTable.knowledge] = knowledge it[AllocationsTable.knowledge] = knowledge
it[AllocationsTable.risk] = risk it[AllocationsTable.risk] = risk
@ -360,7 +360,7 @@ class SummaryModel(val db : Database) {
} }
} }
private fun findOrCreateAuthorGroup(authors : List<String>) : Int = transaction(db) { private fun findOrCreateAuthorGroup(authors: List<String>): Int = transaction(db) {
val authorsstr = authors.joinToString("\n") val authorsstr = authors.joinToString("\n")
var authorGroupId = AuthorsGroupsTable.select { var authorGroupId = AuthorsGroupsTable.select {
AuthorsGroupsTable.authors eq authorsstr AuthorsGroupsTable.authors eq authorsstr
@ -383,7 +383,7 @@ class SummaryModel(val db : Database) {
authorGroupId authorGroupId
} }
private fun findOrCreateAuthor(author : String) : Int = transaction(db) { private fun findOrCreateAuthor(author: String): Int = transaction(db) {
AuthorsTable.insertIgnore { AuthorsTable.insertIgnore {
it[AuthorsTable.author] = author it[AuthorsTable.author] = author
} }
@ -394,7 +394,7 @@ class SummaryModel(val db : Database) {
}.first() }.first()
} }
private fun createLine(line : String, lineNum : Int, fileId : Int) = transaction(db) { private fun createLine(line: String, lineNum: Int, fileId: Int) = transaction(db) {
LinesTable.insertAndGetId { LinesTable.insertAndGetId {
it[LinesTable.line] = line it[LinesTable.line] = line
it[LinesTable.linenum] = lineNum it[LinesTable.linenum] = lineNum
@ -402,14 +402,14 @@ class SummaryModel(val db : Database) {
}.value }.value
} }
private fun createFile(fname : String, parentDirId : Int) = transaction(db) { private fun createFile(fname: String, parentDirId: Int) = transaction(db) {
FilesTable.insertAndGetId { FilesTable.insertAndGetId {
it[FilesTable.fname] = fname it[FilesTable.fname] = fname
it[FilesTable.dirid] = parentDirId it[FilesTable.dirid] = parentDirId
}.value }.value
} }
private fun findOrCreateProject(project : String) : Int = transaction(db) { private fun findOrCreateProject(project: String): Int = transaction(db) {
ProjectTable.insertIgnore { ProjectTable.insertIgnore {
it[ProjectTable.project] = project it[ProjectTable.project] = project
} }
@ -420,7 +420,7 @@ class SummaryModel(val db : Database) {
}.first() }.first()
} }
private fun findOrCreateDir(dirname : String, projectId : Int, parentDirId : Int) : Int = transaction(db) { private fun findOrCreateDir(dirname: String, projectId: Int, parentDirId: Int): Int = transaction(db) {
DirsTable.insertIgnore { DirsTable.insertIgnore {
it[dir] = dirname it[dir] = dirname
it[parentdirid] = parentDirId it[parentdirid] = parentDirId
@ -435,25 +435,25 @@ class SummaryModel(val db : Database) {
}.first() }.first()
} }
private fun splitAllDirs(dirname : File) = dirname.toPath().iterator().asSequence().toList() private fun splitAllDirs(dirname: File) = dirname.toPath().iterator().asSequence().toList()
private fun adjustFname(repoRoot : File, projectRoot : File, fname : File) : File { private fun adjustFname(repoRoot: File, projectRoot: File, fname: File): File {
val rootDiff = if(projectRoot.canonicalPath != repoRoot.canonicalPath) { val rootDiff = if (projectRoot.canonicalPath != repoRoot.canonicalPath) {
projectRoot.relativeTo(repoRoot) projectRoot.relativeTo(repoRoot)
} else { } else {
repoRoot repoRoot
} }
return if(rootDiff.toString().length != 0) { return if (rootDiff.toString().length != 0) {
fname.relativeTo(rootDiff) fname.relativeTo(rootDiff)
} else { } else {
fname fname
} }
} }
private fun reconsDirs(dirId : Int) = transaction(db) { private fun reconsDirs(dirId: Int) = transaction(db) {
val dirs = mutableListOf<String>() val dirs = mutableListOf<String>()
var parentDirId = dirId var parentDirId = dirId
while(parentDirId != 0) { while (parentDirId != 0) {
DirsTable.select { DirsTable.select {
DirsTable.id eq parentDirId DirsTable.id eq parentDirId
} }
@ -477,4 +477,4 @@ class SummaryModel(val db : Database) {
AllocationsTable AllocationsTable
) )
} }
} }