From d8ca1c5144647ee40ab189ebd546ff541a73ceec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Tue, 4 Jun 2024 13:52:42 +0200 Subject: [PATCH] Add String resource mover cli script --- cli/resource-mover-cli/README.md | 17 ++ cli/resource-mover-cli/build.gradle.kts | 13 ++ .../thunderbird/cli/resource/mover/Main.kt | 3 + .../cli/resource/mover/ResourceMoverCli.kt | 29 +++ .../cli/resource/mover/StringResourceMover.kt | 186 ++++++++++++++++++ scripts/resource_mover | 3 + settings.gradle.kts | 1 + 7 files changed, 252 insertions(+) create mode 100644 cli/resource-mover-cli/README.md create mode 100644 cli/resource-mover-cli/build.gradle.kts create mode 100644 cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/Main.kt create mode 100644 cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/ResourceMoverCli.kt create mode 100644 cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/StringResourceMover.kt create mode 100755 scripts/resource_mover diff --git a/cli/resource-mover-cli/README.md b/cli/resource-mover-cli/README.md new file mode 100644 index 0000000000..0bc60893fc --- /dev/null +++ b/cli/resource-mover-cli/README.md @@ -0,0 +1,17 @@ +# Resource Mover CLI + +This is a command line interface that will move resources from one module to another. + +## Usage + +You can run the script with the following command: + +```bash +./scripts/resource-mover --from --to --keys +``` + +The **source-module-path** should be the path to the module that contains the resources you want to move. + +The **target-module-path** should be the path to the module where you want to move the resources. + +The **keys-to-move** should be the keys of the resources you want to move. You can pass multiple keys separated by a comma. diff --git a/cli/resource-mover-cli/build.gradle.kts b/cli/resource-mover-cli/build.gradle.kts new file mode 100644 index 0000000000..c410f16838 --- /dev/null +++ b/cli/resource-mover-cli/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id(ThunderbirdPlugins.App.jvm) +} + +version = "unspecified" + +application { + mainClass.set("net.thunderbird.cli.resource.mover.MainKt") +} + +dependencies { + implementation(libs.clikt) +} diff --git a/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/Main.kt b/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/Main.kt new file mode 100644 index 0000000000..fcbc8d0f93 --- /dev/null +++ b/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/Main.kt @@ -0,0 +1,3 @@ +package net.thunderbird.cli.resource.mover + +fun main(args: Array) = ResourceMoverCli().main(args) diff --git a/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/ResourceMoverCli.kt b/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/ResourceMoverCli.kt new file mode 100644 index 0000000000..a8df077283 --- /dev/null +++ b/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/ResourceMoverCli.kt @@ -0,0 +1,29 @@ +package net.thunderbird.cli.resource.mover + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.options.split + +class ResourceMoverCli( + private val stringResourceMover: StringResourceMover = StringResourceMover(), +) : CliktCommand( + name = "resource-mover", + help = "Move string resources from one file to another", +) { + private val from: String by option( + help = "Source module path", + ).required() + + private val to: String by option( + help = "Target module path", + ).required() + + private val keys: List by option( + help = "Keys to move", + ).split(",").required() + + override fun run() { + stringResourceMover.moveKeys(from, to, keys) + } +} diff --git a/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/StringResourceMover.kt b/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/StringResourceMover.kt new file mode 100644 index 0000000000..ddfb264488 --- /dev/null +++ b/cli/resource-mover-cli/src/main/kotlin/net/thunderbird/cli/resource/mover/StringResourceMover.kt @@ -0,0 +1,186 @@ +package net.thunderbird.cli.resource.mover + +import java.io.File +import kotlin.system.exitProcess + +@Suppress("TooManyFunctions") +class StringResourceMover { + + fun moveKeys(source: String, target: String, keys: List) { + val sourcePath = File(source + RESOURCE_PATH) + val targetPath = File(target + RESOURCE_PATH) + + if (!sourcePath.exists()) { + println("\nSource path does not exist: $sourcePath\n") + return + } + + println("\nMoving keys $keys") + println(" from \"$sourcePath\" -> \"$targetPath\"\n") + for (key in keys) { + moveKey(sourcePath, targetPath, key) + } + } + + private fun moveKey(sourcePath: File, targetPath: File, key: String) { + println("\nMoving key: $key\n") + + sourcePath.walk() + .filter { it.name.startsWith(VALUES_PATH) } + .forEach { sourceDir -> + val sourceFile = sourceDir.resolve(STRING_RESOURCE_FILE_NAME) + if (sourceFile.exists()) { + moveKeyDeclaration(sourceFile, targetPath, key) + } + } + } + + private fun moveKeyDeclaration(sourceFile: File, targetPath: File, key: String) { + if (containsKey(sourceFile, key)) { + println("\nFound key in file: ${sourceFile.path}\n") + + val targetFile = getOrCreateTargetFile(targetPath, sourceFile) + val keyDeclaration = extractKeyDeclaration(sourceFile, key) + + println(" Key declaration: $keyDeclaration") + + copyKeyToTarget(targetFile, keyDeclaration, key) + deleteKeyFromSource(sourceFile, keyDeclaration) + + if (isSourceFileEmpty(sourceFile)) { + println(" Source file is empty: ${sourceFile.path} -> deleting it.") + sourceFile.delete() + } + } + } + + private fun containsKey(sourceFile: File, key: String): Boolean { + val keyPattern = createKeyPattern(key) + val sourceContent = sourceFile.readText() + return keyPattern.containsMatchIn(sourceContent) + } + + private fun extractKeyDeclaration(sourceFile: File, key: String): String { + val keyPattern = createKeyPattern(key) + val declaration = StringBuilder() + var isTagClosed = true + + sourceFile.forEachLine { line -> + if (keyPattern.containsMatchIn(line)) { + declaration.appendLine(line) + isTagClosed = isTagClosed(line) + } else if (!isTagClosed) { + declaration.appendLine(line) + isTagClosed = isTagClosed(line) + } + } + + return declaration.toString() + } + + private fun createKeyPattern(key: String): Regex { + return KEY_PATTERN.replace(KEY_PLACEHOLDER, Regex.escape(key)).toRegex() + } + + private fun isTagClosed(line: String): Boolean { + return line.contains(STRING_CLOSING_TAG) || line.contains(PLURALS_CLOSING_TAG) + } + + private fun copyKeyToTarget(targetFile: File, keyDeclaration: String, key: String) { + println(" Moving key to file: ${targetFile.path}") + + if (containsKey(targetFile, key)) { + println(" Key already exists in target file: ${targetFile.path} replacing it.") + replaceKeyInTarget(targetFile, keyDeclaration, key) + } else { + addKeyToTarget(targetFile, keyDeclaration) + } + } + + private fun addKeyToTarget(targetFile: File, keyDeclaration: String) { + val targetContent = StringBuilder() + + targetFile.forEachLine { line -> + if (line.contains(RESOURCE_CLOSING_TAG)) { + targetContent.appendLine(keyDeclaration.trimEnd()) + targetContent.appendLine(line) + } else { + targetContent.appendLine(line) + } + } + + targetFile.writeText(targetContent.toString()) + } + + private fun replaceKeyInTarget(targetFile: File, keyDeclaration: String, key: String) { + println(" Replacing key in file: ${targetFile.path}") + + val oldKeyDeclaration = extractKeyDeclaration(targetFile, key) + val targetContent = targetFile.readText() + + targetFile.writeText(targetContent.replace(oldKeyDeclaration, keyDeclaration)) + } + + private fun deleteKeyFromSource(sourceFile: File, keyDeclaration: String) { + println(" Deleting key from file: ${sourceFile.path}") + + val sourceContent = sourceFile.readText() + + sourceFile.writeText(sourceContent.replace(keyDeclaration, "")) + } + + private fun isSourceFileEmpty(sourceFile: File): Boolean { + val sourceContent = sourceFile.readText() + return sourceContent.contains(STRING_CLOSING_TAG).not() && sourceContent.contains(PLURALS_CLOSING_TAG).not() + } + + private fun getOrCreateTargetFile(targetPath: File, sourceFile: File): File { + val targetFilePath = targetPath.resolve(sourceFile.parentFile.name) + val targetFile = File(targetFilePath, sourceFile.name) + val targetDirectory = targetFile.parentFile + + if (!targetDirectory.exists()) { + targetDirectory.mkdirs() + println(" Target directory created: ${targetDirectory.path}") + } + + if (!targetFile.exists()) { + createTargetFile(targetFile) + } + + return targetFile + } + + private fun createTargetFile(targetFile: File) { + val isNewFileCreated: Boolean = targetFile.createNewFile() + if (!isNewFileCreated) { + printError("Target file could not be created: ${targetFile.path}") + exitProcess(-1) + } + + targetFile.writeText(TARGET_FILE_CONTENT) + println("Target file ${targetFile.path} created") + } + + private fun printError(message: String) { + System.err.println("\n$message\n") + } + + private companion object { + const val RESOURCE_PATH = "/src/main/res/" + const val KEY_PLACEHOLDER = "{KEY}" + const val KEY_PATTERN = """name="$KEY_PLACEHOLDER"""" + const val VALUES_PATH = "values" + const val STRING_RESOURCE_FILE_NAME = "strings.xml" + const val STRING_CLOSING_TAG = "" + const val PLURALS_CLOSING_TAG = "" + const val RESOURCE_CLOSING_TAG = "" + + val TARGET_FILE_CONTENT = """ + + + + + """.trimIndent() + } +} diff --git a/scripts/resource_mover b/scripts/resource_mover new file mode 100755 index 0000000000..c47d7dd0b3 --- /dev/null +++ b/scripts/resource_mover @@ -0,0 +1,3 @@ +#!/bin/sh + +./gradlew --quiet ":cli:resource-mover-cli:installDist" < /dev/null && ./cli/resource-mover-cli/build/install/resource-mover-cli/bin/resource-mover-cli "$@" diff --git a/settings.gradle.kts b/settings.gradle.kts index d9870fb33e..b24672d881 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -115,6 +115,7 @@ include(":plugins:openpgp-api-lib:openpgp-api") include( ":cli:autodiscovery-cli", ":cli:html-cleaner-cli", + ":cli:resource-mover-cli", ":cli:translation-cli", )