Skip to content
Open
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
33 changes: 28 additions & 5 deletions cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.source.encoding>UTF-8</project.source.encoding>
<picocli.version>4.7.7</picocli.version>
</properties>

<dependencies>
Expand All @@ -27,16 +28,16 @@
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>${picocli.version}</version>
</dependency>
<dependency>
<groupId>io.github.ardoco</groupId>
<artifactId>metrics</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-cli-jvm</artifactId>
<version>0.3.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
Expand Down Expand Up @@ -81,6 +82,28 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>kapt</id>
<goals>
<goal>kapt</goal>
</goals>
<configuration combine.self="append">
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>info.picocli</groupId>
<artifactId>picocli-codegen</artifactId>
<version>${picocli.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
28 changes: 15 additions & 13 deletions cli/src/main/kotlin/edu/kit/kastel/mcse/ardoco/metrics/cli/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ import edu.kit.kastel.mcse.ardoco.metrics.cli.commands.AggregationClassification
import edu.kit.kastel.mcse.ardoco.metrics.cli.commands.AggregationRankCommand
import edu.kit.kastel.mcse.ardoco.metrics.cli.commands.ClassificationCommand
import edu.kit.kastel.mcse.ardoco.metrics.cli.commands.RankCommand
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.ExperimentalCli
import picocli.CommandLine

@OptIn(ExperimentalCli::class)
fun main(args: Array<String>) {
val parser = ArgParser("ArDoCo Metrics")

val outputFileOption = parser.option(ArgType.String, shortName = "o", description = "The output file", fullName = "output")
val classificationCommand = ClassificationCommand(outputFileOption)
val aggregationClassificationCommand = AggregationClassificationCommand(outputFileOption)
val rankCommand = RankCommand(outputFileOption)
val aggregationRankCommand = AggregationRankCommand(outputFileOption)
parser.subcommands(classificationCommand, aggregationClassificationCommand, rankCommand, aggregationRankCommand)
parser.parse(args)
val rootCommand = RootCommand()
CommandLine(rootCommand)
.addSubcommand("classification", ClassificationCommand())
.addSubcommand("aggCl", AggregationClassificationCommand())
.addSubcommand("rank", RankCommand())
.addSubcommand("aggRnk", AggregationRankCommand())
.execute(*args)
}

@CommandLine.Command(
name = "ArDoCo Metrics",
mixinStandardHelpOptions = true,
description = ["CLI for ArDoCo Metrics"]
)
class RootCommand
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,27 @@ import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import edu.kit.kastel.mcse.ardoco.metrics.ClassificationMetricsCalculator
import edu.kit.kastel.mcse.ardoco.metrics.result.SingleClassificationResult
import kotlinx.cli.ArgType
import kotlinx.cli.ExperimentalCli
import kotlinx.cli.SingleNullableOption
import kotlinx.cli.Subcommand
import kotlinx.cli.required
import java.io.File
import picocli.CommandLine.Command
import picocli.CommandLine.Option
import java.util.concurrent.Callable

@OptIn(ExperimentalCli::class)
class AggregationClassificationCommand(
private val outputFileOption: SingleNullableOption<String>
) : Subcommand("aggCl", "Aggregate results of multiple classifications. I.e., Macro Average + WeightedAverage + Micro Average") {
private val directoryWithResultsOption by option(
ArgType.String,
shortName = "d",
description = "The directory with the classification results"
).required()
@Command(
name = "aggCl",
description = ["Aggregate results of multiple classifications. I.e., Macro Average + WeightedAverage + Micro Average"],
mixinStandardHelpOptions = true
)
class AggregationClassificationCommand : Callable<Int> {
@Option(names = ["-d", "--directory"], description = ["The directory with the classification results"], required = true)
lateinit var directoryWithResults: String

override fun execute() {
val directory = File(directoryWithResultsOption)
@Option(names = ["-o", "--output"], description = ["The output file"])
var outputFile: String? = null

override fun call(): Int {
val directory = java.io.File(directoryWithResults)
if (!directory.isDirectory) {
println("The provided path is not a directory")
return
return 1
}
val oom = ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT).registerKotlinModule()
val results: List<SingleClassificationResult<String>> =
Expand All @@ -38,16 +37,15 @@ class AggregationClassificationCommand(
} ?: emptyList()
if (results.isEmpty()) {
println("No classification results found")
return
return 1
}
val classificationMetrics = ClassificationMetricsCalculator.Instance
val average = classificationMetrics.calculateAverages(results)
average.forEach { it.prettyPrint() }

val output = outputFileOption.value
if (output != null) {
val outputFile = File(output)
oom.writeValue(outputFile, average)
outputFile?.let {
val outputFileObj = java.io.File(it)
oom.writeValue(outputFileObj, average)
}
return 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,27 @@ import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import edu.kit.kastel.mcse.ardoco.metrics.RankMetricsCalculator
import edu.kit.kastel.mcse.ardoco.metrics.result.SingleRankMetricsResult
import kotlinx.cli.ArgType
import kotlinx.cli.ExperimentalCli
import kotlinx.cli.SingleNullableOption
import kotlinx.cli.Subcommand
import kotlinx.cli.required
import java.io.File
import picocli.CommandLine.Command
import picocli.CommandLine.Option
import java.util.concurrent.Callable

@OptIn(ExperimentalCli::class)
class AggregationRankCommand(
private val outputFileOption: SingleNullableOption<String>
) : Subcommand("aggRnk", "Aggregate results of multiple rank metrics runs. I.e., Macro Average + WeightedAverage") {
private val directoryWithResultsOption by option(
ArgType.String,
shortName = "d",
description = "The directory with the rank results"
).required()
@Command(
name = "aggRnk",
description = ["Aggregate results of multiple rank metrics runs. I.e., Macro Average + WeightedAverage"],
mixinStandardHelpOptions = true
)
class AggregationRankCommand : Callable<Int> {
@Option(names = ["-d", "--directory"], description = ["The directory with the rank results"], required = true)
lateinit var directoryWithResults: String

override fun execute() {
val directory = File(directoryWithResultsOption)
@Option(names = ["-o", "--output"], description = ["The output file"])
var outputFile: String? = null

override fun call(): Int {
val directory = java.io.File(directoryWithResults)
if (!directory.isDirectory) {
println("The provided path is not a directory")
return
return 1
}
val oom = ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT).registerKotlinModule()
val results: List<SingleRankMetricsResult> =
Expand All @@ -38,16 +37,15 @@ class AggregationRankCommand(
} ?: emptyList()
if (results.isEmpty()) {
println("No classification results found")
return
return 1
}
val rankMetrics = RankMetricsCalculator.Instance
val average = rankMetrics.calculateAverages(results)
average.forEach { it.prettyPrint() }

val output = outputFileOption.value
if (output != null) {
val outputFile = File(output)
oom.writeValue(outputFile, average)
outputFile?.let {
val outputFileObj = java.io.File(it)
oom.writeValue(outputFileObj, average)
}
return 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,60 @@ package edu.kit.kastel.mcse.ardoco.metrics.cli.commands
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import edu.kit.kastel.mcse.ardoco.metrics.ClassificationMetricsCalculator
import kotlinx.cli.ArgType
import kotlinx.cli.ExperimentalCli
import kotlinx.cli.SingleNullableOption
import kotlinx.cli.Subcommand
import kotlinx.cli.default
import kotlinx.cli.required
import java.io.File
import picocli.CommandLine.Command
import picocli.CommandLine.Option
import java.util.concurrent.Callable

@OptIn(ExperimentalCli::class)
class ClassificationCommand(private val outputFileOption: SingleNullableOption<String>) : Subcommand("classification", "Calculates classification metrics") {
private val classificationFileOption by option(ArgType.String, shortName = "c", description = "The classification file", fullName = "classification").required()
private val groundTruthFileOption by option(ArgType.String, shortName = "g", description = "The ground truth file", fullName = "ground-truth").required()
private val fileHeaderOption by option(ArgType.Boolean, description = "Whether the files have a header", fullName = "header").default(false)
private val confusionMatrixSumOption by option(ArgType.Int, shortName = "s", description = "The sum of the confusion matrix", fullName = "sum").default(-1)
@Command(name = "classification", description = ["Calculates classification metrics"], mixinStandardHelpOptions = true)
class ClassificationCommand : Callable<Int> {
@Option(names = ["-c", "--classification"], description = ["The classification file"], required = true)
lateinit var classificationFile: String

override fun execute() {
println("Calculating classification metrics")
val classificationFile = File(classificationFileOption)
val groundTruthFile = File(groundTruthFileOption)
@Option(names = ["-g", "--ground-truth"], description = ["The ground truth file"], required = true)
lateinit var groundTruthFile: String

if (!classificationFile.exists() || !groundTruthFile.exists()) {
println("Classification file or ground truth file does not exist")
return
}
@Option(names = ["--header"], description = ["Whether the files have a header"])
var fileHeader: Boolean = false

val classification = classificationFile.readLines().filter { it.isNotBlank() }.drop(if (fileHeaderOption) 1 else 0).toSet()
val groundTruth = groundTruthFile.readLines().filter { it.isNotBlank() }.drop(if (fileHeaderOption) 1 else 0).toSet()
@Option(names = ["-s", "--sum"], description = ["The sum of the confusion matrix"])
var confusionMatrixSum: Int = -1

val classificationMetrics = ClassificationMetricsCalculator.Instance
@Option(names = ["-o", "--output"], description = ["The output file"])
var outputFile: String? = null

override fun call(): Int {
println("Calculating classification metrics")
val classificationFileObj = java.io.File(classificationFile)
val groundTruthFileObj = java.io.File(groundTruthFile)
if (!classificationFileObj.exists() || !groundTruthFileObj.exists()) {
println("Classification file or ground truth file does not exist")
return 1
}
val classification =
classificationFileObj
.readLines()
.filter { it.isNotBlank() }
.drop(if (fileHeader) 1 else 0)
.toSet()
val groundTruth =
groundTruthFileObj
.readLines()
.filter { it.isNotBlank() }
.drop(if (fileHeader) 1 else 0)
.toSet()
val classificationMetrics = edu.kit.kastel.mcse.ardoco.metrics.ClassificationMetricsCalculator.Instance
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fully qualified class name is unnecessarily verbose. Since ClassificationMetricsCalculator is already imported, you can use 'ClassificationMetricsCalculator.Instance' instead.

Suggested change
val classificationMetrics = edu.kit.kastel.mcse.ardoco.metrics.ClassificationMetricsCalculator.Instance
val classificationMetrics = ClassificationMetricsCalculator.Instance

Copilot uses AI. Check for mistakes.

val result =
classificationMetrics.calculateMetrics(
classification,
groundTruth,
if (confusionMatrixSumOption < 0) null else confusionMatrixSumOption
if (confusionMatrixSum < 0) null else confusionMatrixSum
)
result.prettyPrint()

val output = outputFileOption.value
if (output != null) {
val outputFile = File(output)
outputFile?.let {
val outputFileObj = java.io.File(it)
val oom = ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT).registerKotlinModule()
oom.writeValue(outputFile, result)
oom.writeValue(outputFileObj, result)
}
return 0
}
}
Loading