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

Compare command chores #7947

Merged
merged 5 commits into from
Nov 28, 2023
Merged
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
46 changes: 29 additions & 17 deletions plugins/commands/compare/src/main/kotlin/CompareCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import com.github.ajalt.clikt.parameters.types.file
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.mordant.rendering.Theme
import com.github.difflib.DiffUtils
import com.github.difflib.UnifiedDiffUtils
Expand All @@ -51,6 +52,8 @@ class CompareCommand : OrtCommand(
name = "compare",
help = "Compare two ORT results with various methods."
) {
private enum class CompareMethod { SEMANTIC_DIFF, TEXT_DIFF }

private val fileA by argument(help = "The first ORT result file to compare.")
.convert { it.expandTilde() }
.file(mustExist = true, canBeFile = true, canBeDir = false, mustBeWritable = false, mustBeReadable = true)
Expand All @@ -68,6 +71,13 @@ class CompareCommand : OrtCommand(
).enum<CompareMethod>()
.default(CompareMethod.TEXT_DIFF)

private val contextSize by option(
"--context-size", "-C",
help = "The number of unmodified lines to display in the context of a modified line. Only applies to unified " +
"diff output."
).int()
.default(7)

private val ignoreTime by option(
"--ignore-time", "-t",
help = "Ignore time differences."
Expand All @@ -91,19 +101,20 @@ class CompareCommand : OrtCommand(

if (fileA.extension != fileB.extension) {
echo("The file arguments need to be of the same type.")
throw ProgramResult(2)
throw ProgramResult(1)
}

val deserializer = fileA.mapper().registerModule(
// Arbitrarily determine the mapper from the first file as the file extensions are ensured to be the same.
val mapper = fileA.mapper().registerModule(
SimpleModule().apply {
// TODO: Find a way to also ignore temporary directories.
// TODO: Find a way to also ignore temporary directories (when diffing semantically).
if (ignoreTime) addDeserializer(Instant::class.java, EpochInstantDeserializer())
if (ignoreEnvironment) addDeserializer(Environment::class.java, DefaultEnvironmentDeserializer())
}
)

val resultA = deserializer.readValue<OrtResult>(fileA)
val resultB = deserializer.readValue<OrtResult>(fileB)
val resultA = mapper.readValue<OrtResult>(fileA)
val resultB = mapper.readValue<OrtResult>(fileB)

when (method) {
CompareMethod.SEMANTIC_DIFF -> {
Expand All @@ -119,12 +130,12 @@ class CompareCommand : OrtCommand(
throw ProgramResult(0)
}

throw ProgramResult(1)
throw ProgramResult(2)
}

CompareMethod.TEXT_DIFF -> {
val textA = deserializer.writeValueAsString(resultA)
val textB = deserializer.writeValueAsString(resultB)
val textA = mapper.writeValueAsString(resultA)
val textB = mapper.writeValueAsString(resultB)

// Apply data type independent replacements in the texts.
val replacements = buildMap {
Expand All @@ -145,7 +156,7 @@ class CompareCommand : OrtCommand(
"b/${fileB.relativeTo(commonParent).invariantSeparatorsPath}",
linesA,
DiffUtils.diff(linesA, linesB),
/* contextSize = */ 7
contextSize
)

if (diff.isEmpty()) {
Expand All @@ -159,25 +170,26 @@ class CompareCommand : OrtCommand(
echo(it)
}

throw ProgramResult(1)
throw ProgramResult(2)
}
}
}
}

private enum class CompareMethod {
SEMANTIC_DIFF,
TEXT_DIFF
}

private class EpochInstantDeserializer : StdDeserializer<Instant>(Instant::class.java) {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Instant =
Instant.EPOCH.also { parser.codec.readTree<JsonNode>(parser) }
Instant.EPOCH.also {
// Just consume the JSON text node without actually using it.
parser.codec.readTree<JsonNode>(parser)
}
}

private class DefaultEnvironmentDeserializer : StdDeserializer<Environment>(Environment::class.java) {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Environment =
Environment().also { parser.codec.readTree<JsonNode>(parser) }
Environment().also {
// Just consume the JSON object node without actually using it.
parser.codec.readTree<JsonNode>(parser)
}
}

private fun Map<Regex, String>.replaceIn(text: String) =
Expand Down