Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI Foundation #6

Merged
merged 4 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,28 @@ kotlin {

val commonMain by getting {
dependencies {
val ktorVersion = "2.3.+"
implementation(kotlin("stdlib-common"))
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.+")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.+")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.+")
implementation("io.ktor:ktor-client-core:2.3.+")
implementation("io.ktor:ktor-client-auth:2.3.+")
implementation("io.ktor:ktor-client-logging:2.3.+")
implementation("io.ktor:ktor-client-serialization:2.3.+")
implementation("io.ktor:ktor-client-content-negotiation:2.3.+")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.+")
implementation("com.squareup.okio:okio:3.4.+")
implementation("net.mamoe.yamlkt:yamlkt:0.+")
implementation("com.apollographql.apollo3:apollo-api:4.+")
implementation("com.apollographql.apollo3:apollo-runtime:4.+")
implementation("com.github.ajalt.clikt:clikt:4.2.+")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-server-core:$ktorVersion")
implementation("io.ktor:ktor-server-host-common:$ktorVersion")
implementation("io.ktor:ktor-server-cio:$ktorVersion")
implementation("io.ktor:ktor-server-cors:$ktorVersion")
implementation("io.ktor:ktor-server-forwarded-header:$ktorVersion")
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
}
}

Expand All @@ -65,6 +73,7 @@ kotlin {
implementation(kotlin("test"))
implementation("com.squareup.okio:okio-fakefilesystem:3.4.+")
implementation("com.willowtreeapps.assertk:assertk:0.+")
implementation("io.ktor:ktor-server-test-host:2.3.+")
}
}

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ org.gradle.jvmargs=-Xmx4096m
# Project properties
config.group = xyz.marinkovic.milos
config.artifact = codestats
config.version = 0.5.0
config.version = 0.6.0
config.gitHubRepoOwner = milosmns
config.gitHubRepoName = code-stats
120 changes: 22 additions & 98 deletions src/commonMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,102 +1,26 @@
import calculator.di.provideGenericLongMetricCalculators
import commands.CommandLineArguments
import commands.CommandLineArguments.Mode.FETCH
import commands.CommandLineArguments.Mode.PRINT
import commands.CommandLineArguments.Mode.PURGE
import commands.CommandLineArguments.Mode.REPORT
import commands.CommandLineArguments.Mode.SERVE
import commands.di.provideFetchCommand
import commands.di.providePrintCommand
import commands.di.providePurgeCommand
import commands.di.provideReportCommand
import commands.di.provideServeCommand
import components.data.TeamHistoryConfig
import history.TeamHistory
import history.filter.transform.RepositoryDateTransform
import history.github.di.provideGitHubHistory
import history.github.di.provideGitHubHistoryConfig
import history.storage.di.provideStoredHistory
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.LocalDate
import utils.fromFile

fun main(): Unit = runBlocking {
/*************************************************************************
THESE ARE TEMPORARY EXPERIMENTS, NOT PART OF THE FINAL PRODUCT
*************************************************************************/

println("\n== Code Stats CLI ==\n")

print("Loading configuration... ")
val teamHistoryConfig = TeamHistoryConfig.fromFile("src/commonMain/resources/sample.config.yaml")
println("Done.")

println(teamHistoryConfig.simpleFormat)

val history: TeamHistory = provideGitHubHistory(
teamHistoryConfig = teamHistoryConfig,
gitHubHistoryConfig = provideGitHubHistoryConfig(),
)

try {
// NETWORK EXPERIMENTS
// println("Loading team history...")
// val fetched = mutableListOf<Repository>()
// teamHistoryConfig.teams.forEach { team ->
// println("Loading for team ${team.title}...")
// team.discussionRepositories.forEach { repoName ->
// println("Loading discussion repository $repoName...")
// fetched += history.fetchRepository(repoName, includeCodeReviews = false, includeDiscussions = true)
// }
// team.codeRepositories.forEach { repoName ->
// println("Loading code repository $repoName...")
// fetched += history.fetchRepository(repoName, includeCodeReviews = true, includeDiscussions = false)
// }
// }

// STORAGE EXPERIMENTS
val storage = provideStoredHistory(teamHistoryConfig)
// storage.purgeAll()
// val storedUnsorted = mutableListOf<Repository>()
// fetched.forEach {
// storage.storeRepositoryDeep(it)
// storedUnsorted += storage.fetchRepository(
// it.name,
// includeCodeReviews = true,
// includeDiscussions = true,
// )
// }

val stored = storage.fetchAllRepositories().map {
storage.fetchRepository(
it.name,
includeCodeReviews = true,
includeDiscussions = true,
)
}

stored.forEach { repo ->
println(repo.simpleFormat)
repo.codeReviews.forEach {
println("\t r#${it.number} ${it.createdAt} >> ${it.mergedAt} >> ${it.closedAt}")
}
repo.discussions.forEach {
println("\t d#${it.number} ${it.createdAt} >> ${it.closedAt}")
}
println("-- ${repo.fullName} --\n")
}

println("Filter by date? DD.MM.YYYY (empty for no filter)")
val dateString = readln().trim()
val filtered = if (dateString.isNotEmpty()) {
val day = dateString.substringBefore(".").toInt()
val month = dateString.substringAfter(".").substringBefore(".").toInt()
val year = dateString.substringAfterLast(".").toInt()
val date = LocalDate(year, month, day)
val transform = RepositoryDateTransform(date, date)
stored.map(transform)
} else stored

// OTHER EXPERIMENTS
provideGenericLongMetricCalculators().forEach {
val metric = it.calculate(filtered)
println(metric.simpleFormat)
println("-- ${metric.name} --\n")
}
} catch (e: Throwable) {
println("CRITICAL FAILURE! \n\n * ${e.message} * \n\n")
e.printStackTrace()
} finally {
history.close()
}

fun main(args: Array<String>) {
val arguments = CommandLineArguments().load(args)
val teamHistoryConfig = TeamHistoryConfig.fromFile(arguments.configFile)

when (arguments.mode) {
SERVE -> provideServeCommand(teamHistoryConfig)
FETCH -> provideFetchCommand(teamHistoryConfig)
REPORT -> provideReportCommand(teamHistoryConfig)
PRINT -> providePrintCommand(teamHistoryConfig)
PURGE -> providePurgeCommand(teamHistoryConfig)
}.run()
}
38 changes: 38 additions & 0 deletions src/commonMain/kotlin/commands/CommandLineArguments.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package commands

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.help
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import commands.CommandLineArguments.Mode.REPORT

class CommandLineArguments : CliktCommand(
name = "codestats",
help = "Code Stats Utility. Run with --help for more.",
) {

enum class Mode { SERVE, FETCH, REPORT, PRINT, PURGE }

val mode: Mode by option()
.enum<Mode>(ignoreCase = true) { it.name.lowercase() }
.default(REPORT)
.help(
listOf(
"[Serve] launches a server and prints the access URL",
"[Fetch] fetches fresh git data and overwrites the stored history",
"[Report] a short report on what data is available",
"[Print] calculates and prints the code stats to stdout",
"[Purge] deletes all previously stored data",
).joinToString(". ")
)

val configFile: String by option()
.default("src/commonMain/resources/sample.config.yaml")
.help("Path to the configuration YAML file")

override fun run() = Unit

fun load(args: Array<String>) = apply { main(args) }

}
68 changes: 68 additions & 0 deletions src/commonMain/kotlin/commands/cli/FetchCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package commands.cli

import components.data.Repository
import components.data.TeamHistoryConfig
import history.TeamHistory
import history.storage.StoredHistory
import history.storage.config.StorageConfig
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.runBlocking

class FetchCommand(
private val teamHistoryConfig: TeamHistoryConfig,
private val teamHistory: TeamHistory,
private val storageConfig: StorageConfig,
private val storedHistory: StoredHistory,
) : Runnable {

override fun run() = runBlocking {
println("== File configuration ==")
println(teamHistoryConfig.simpleFormat)

println("\n== History fetch ==")
val fetched = mutableListOf<Repository>()
try {
print("This will start a new data fetch and may impact rate limiting. Continue? (y/n) ")
if (readln().lowercase().trim() != "y") {
println("Aborted. No requests were executed.")
return@runBlocking
}
teamHistoryConfig.teams.forEach { team ->
println("Looking into team ${team.title}...")
team.discussionRepositories.forEach {
println("Fetching discussion history for $it...")
fetched += teamHistory.fetchRepository(
repository = it,
includeCodeReviews = false,
includeDiscussions = true,
)
}
team.codeRepositories.forEach {
println("Fetching code/review history for $it...")
fetched += teamHistory.fetchRepository(
repository = it,
includeCodeReviews = true,
includeDiscussions = false,
)
}
}
println("Done. Fetched ${fetched.size} repositories.")
} catch (t: Throwable) {
println("Failed to complete the task. Error: ${t.message}")
return@runBlocking
} finally {
teamHistory.close()
}

println("\n== History storage ==")
print("Storing fetched data will overwrite existing data. Continue? (y/n) ")
if (readln().lowercase().trim() != "y") {
println("Aborted. If rate limiter is applied to your git remote, wait before trying again.")
return@runBlocking
}
println("Now storing ${fetched.size} repositories locally...")
fetched.forEach(storedHistory::storeRepositoryDeep)
println("\nDone. Your fetched data is at ${storageConfig.databasePath}")
}

}
40 changes: 40 additions & 0 deletions src/commonMain/kotlin/commands/cli/PrintCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package commands.cli

import calculator.di.provideGenericLongMetricCalculators
import components.data.TeamHistoryConfig
import history.filter.transform.RepositoryDateTransform
import history.storage.StoredHistory
import kotlinx.coroutines.Runnable

class PrintCommand(
private val teamHistoryConfig: TeamHistoryConfig,
private val storedHistory: StoredHistory,
) : Runnable {

override fun run() {
println("== File configuration ==")
println(teamHistoryConfig.simpleFormat)

println("\n== Metrics ==")
val storedRepos = storedHistory.fetchAllRepositories().map {
storedHistory.fetchRepository(
name = it.name,
includeCodeReviews = true,
includeDiscussions = true,
)
}
val transform = RepositoryDateTransform(teamHistoryConfig.startDate, teamHistoryConfig.endDate)
val filteredRepos = storedRepos.map(transform)
.filter { repo -> repo.codeReviews.isNotEmpty() || repo.discussions.isNotEmpty() }
print("Print details with date filtering applied? (y/n) ")
val dataSet = if (readln().lowercase().trim() == "y") filteredRepos else storedRepos
println("OK.\n")

provideGenericLongMetricCalculators().forEach { calculator ->
val metric = calculator.calculate(dataSet)
println(metric.simpleFormat)
println("-- ${metric.name} --\n")
}
}

}
28 changes: 28 additions & 0 deletions src/commonMain/kotlin/commands/cli/PurgeCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package commands.cli

import components.data.TeamHistoryConfig
import history.storage.StoredHistory
import kotlinx.coroutines.Runnable

class PurgeCommand(
private val teamHistoryConfig: TeamHistoryConfig,
private val storedHistory: StoredHistory,
) : Runnable {

override fun run() {
println("== File configuration ==")
println(teamHistoryConfig.simpleFormat)

println("\n== Purge ==")
println("Purging will permanently delete all locally stored data.")
print("There is no data recovery. Continue? (y/n) ")
if (readln().lowercase().trim() != "y") {
println("Aborted. No data was deleted.")
return
}
println("Now purging all locally stored data...")
storedHistory.purgeAll()
println("Done.")
}

}
Loading