diff --git a/build.gradle.kts b/build.gradle.kts index fc12ab7..c15ff60 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("jvm") version "1.9.10" + kotlin("plugin.serialization") version "1.9.10" alias(libs.plugins.shadow) } @@ -16,6 +17,7 @@ dependencies { implementation(libs.revanced.patcher) implementation(libs.revanced.library) implementation(libs.kotlinx.coroutines.core) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") implementation(libs.picocli) testImplementation(libs.kotlin.test) diff --git a/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt b/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt index 1886613..124a49e 100644 --- a/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt @@ -3,6 +3,8 @@ package app.revanced.cli.command import app.revanced.patcher.PatchBundleLoader import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.options.PatchOption +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import picocli.CommandLine.* import picocli.CommandLine.Help.Visibility.ALWAYS import java.io.File @@ -69,7 +71,14 @@ internal object ListPatchesCommand : Runnable { ) private var packageName: String? = null - override fun run() { + @Option( + names = ["-j", "--json"], + description = ["Output in machine-readable format. Implies -diopv"], + showDefaultValue = ALWAYS + ) + private var asJson: Boolean = false + + private fun formatHuman(patches: List>>): String { fun Patch.CompatiblePackage.buildString() = buildString { if (withVersions && versions != null) { @@ -124,7 +133,16 @@ internal object ListPatchesCommand : Runnable { } } } + return patches.joinToString("\n\n") { it.buildString() } + } + private fun formatJson(patches: List>>): String { + val data = patches.map { app.revanced.cli.serialization.Patch(it) } + val format = Json { encodeDefaults = false } + return format.encodeToString(data) + } + + override fun run() { fun Patch<*>.filterCompatiblePackages(name: String) = compatiblePackages?.any { it.name == name } ?: withUniversalPatches @@ -134,6 +152,6 @@ internal object ListPatchesCommand : Runnable { val filtered = packageName?.let { patches.filter { (_, patch) -> patch.filterCompatiblePackages(it) } } ?: patches - if (filtered.isNotEmpty()) logger.info(filtered.joinToString("\n\n") { it.buildString() }) + if (filtered.isNotEmpty()) logger.info((if (asJson) formatJson(filtered) else formatHuman(filtered))) } } diff --git a/src/main/kotlin/app/revanced/cli/serialization/schema.kt b/src/main/kotlin/app/revanced/cli/serialization/schema.kt new file mode 100644 index 0000000..ba0b023 --- /dev/null +++ b/src/main/kotlin/app/revanced/cli/serialization/schema.kt @@ -0,0 +1,50 @@ +package app.revanced.cli.serialization + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CompatiblePackage( + val name: String, + @SerialName("compatible_versions") + val compatibleVersions: Set = setOf() +) { + constructor(from: app.revanced.patcher.patch.Patch.CompatiblePackage) : this( + from.name, + from.versions.orEmpty() + ) +} + +@Serializable +data class PatchOption( + val title: String?, + val description: String? = null, + val key: String, + val default: String? = null, + val valid: Map = mapOf(), +) { + constructor(from: app.revanced.patcher.patch.options.PatchOption<*>) : this( + from.title, + from.description, + from.key, + from.default?.toString(), + from.values?.mapValues { it.value.toString() }.orEmpty() + ) +} + +@Serializable +data class Patch( + val index: Int, + val description: String? = null, + val options: List = listOf(), + @SerialName("compatible_packages") + val compatiblePackages: List = listOf(), +) { + constructor(from: IndexedValue>) : this( + from.index, + from.value.description, + from.value.options.values.map(::PatchOption), + from.value.compatiblePackages?.map(::CompatiblePackage).orEmpty(), + ) +} +