From 4a73301eb311966817639b7859e2ee74d2e04ee1 Mon Sep 17 00:00:00 2001 From: Hartmut Bauer Date: Sun, 18 Jan 2026 17:17:53 +0100 Subject: [PATCH 1/8] Umstellung auf Kotlin Multiplatform --- .idea/AndroidProjectSystem.xml | 6 + .idea/artifacts/util_js_0_2_8.xml | 8 ++ .idea/artifacts/util_js_0_3_0_beta_1.xml | 8 ++ .idea/artifacts/util_jvm_0_2_8.xml | 8 ++ .idea/artifacts/util_jvm_0_3_0_beta_1.xml | 8 ++ .idea/artifacts/util_wasm_js_0_2_8.xml | 8 ++ .idea/artifacts/util_wasm_js_0_3_0_beta_1.xml | 8 ++ .idea/artifacts/util_wasm_wasi_0_2_8.xml | 8 ++ .../artifacts/util_wasm_wasi_0_3_0_beta_1.xml | 8 ++ .idea/compiler.xml | 6 + .idea/misc.xml | 2 +- .idea/modules.xml | 8 ++ .idea/modules/util.jvmMain.iml | 8 ++ build.gradle.kts | 124 ++++++++++++++---- buildnumber.properties | 4 +- settings.gradle.kts | 2 +- .../kotlin/net/keinekekse/args/priv.kt | 13 +- .../kotlin/net/keinekekse/args/pub.kt | 9 +- .../kotlin/net/keinekekse/util/Version.kt | 14 +- .../kotlin/net/keinekekse/util/delegate.kt | 0 .../kotlin/net/keinekekse/version/Version.kt | 0 .../net/keinekekse/args/annotation/api.kt | 0 .../net/keinekekse/args/annotation/impl.kt | 0 .../kotlin/net/keinekekse/args/priv.jvm.kt | 7 + .../net/keinekekse/args/v1/arguments.kt | 0 .../kotlin/net/keinekekse/config/init.kt | 0 .../net/keinekekse/config/system_config.kt | 0 .../net/keinekekse/config/user_config.kt | 0 .../kotlin/net/keinekekse/exec/exec.kt | 0 .../kotlin/net/keinekekse/util/net.kt | 0 .../keinekekse/version/manifest/version.kt | 0 .../net/keinekekse/args/priv.linuxX64.kt | 8 ++ .../net/keinekekse/args/priv.macosArm64.kt | 8 ++ .../net/keinekekse/args/priv.wasmWasi.kt | 5 + .../kotlin/net/keinekekse/args/priv.web.kt | 5 + 35 files changed, 246 insertions(+), 47 deletions(-) create mode 100644 .idea/AndroidProjectSystem.xml create mode 100644 .idea/artifacts/util_js_0_2_8.xml create mode 100644 .idea/artifacts/util_js_0_3_0_beta_1.xml create mode 100644 .idea/artifacts/util_jvm_0_2_8.xml create mode 100644 .idea/artifacts/util_jvm_0_3_0_beta_1.xml create mode 100644 .idea/artifacts/util_wasm_js_0_2_8.xml create mode 100644 .idea/artifacts/util_wasm_js_0_3_0_beta_1.xml create mode 100644 .idea/artifacts/util_wasm_wasi_0_2_8.xml create mode 100644 .idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/modules/util.jvmMain.iml rename src/{main => commonMain}/kotlin/net/keinekekse/args/priv.kt (97%) rename src/{main => commonMain}/kotlin/net/keinekekse/args/pub.kt (98%) rename src/{main => commonMain}/kotlin/net/keinekekse/util/Version.kt (51%) rename src/{main => commonMain}/kotlin/net/keinekekse/util/delegate.kt (100%) rename src/{main => commonMain}/kotlin/net/keinekekse/version/Version.kt (100%) rename src/{main => jvmMain}/kotlin/net/keinekekse/args/annotation/api.kt (100%) rename src/{main => jvmMain}/kotlin/net/keinekekse/args/annotation/impl.kt (100%) create mode 100644 src/jvmMain/kotlin/net/keinekekse/args/priv.jvm.kt rename src/{main => jvmMain}/kotlin/net/keinekekse/args/v1/arguments.kt (100%) rename src/{main => jvmMain}/kotlin/net/keinekekse/config/init.kt (100%) rename src/{main => jvmMain}/kotlin/net/keinekekse/config/system_config.kt (100%) rename src/{main => jvmMain}/kotlin/net/keinekekse/config/user_config.kt (100%) rename src/{main => jvmMain}/kotlin/net/keinekekse/exec/exec.kt (100%) rename src/{main => jvmMain}/kotlin/net/keinekekse/util/net.kt (100%) rename src/{main => jvmMain}/kotlin/net/keinekekse/version/manifest/version.kt (100%) create mode 100644 src/linuxX64Main/kotlin/net/keinekekse/args/priv.linuxX64.kt create mode 100644 src/macosArm64Main/kotlin/net/keinekekse/args/priv.macosArm64.kt create mode 100644 src/wasmWasiMain/kotlin/net/keinekekse/args/priv.wasmWasi.kt create mode 100644 src/webMain/kotlin/net/keinekekse/args/priv.web.kt diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_js_0_2_8.xml b/.idea/artifacts/util_js_0_2_8.xml new file mode 100644 index 0000000..0ba6a2b --- /dev/null +++ b/.idea/artifacts/util_js_0_2_8.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_js_0_3_0_beta_1.xml b/.idea/artifacts/util_js_0_3_0_beta_1.xml new file mode 100644 index 0000000..4bbd519 --- /dev/null +++ b/.idea/artifacts/util_js_0_3_0_beta_1.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_jvm_0_2_8.xml b/.idea/artifacts/util_jvm_0_2_8.xml new file mode 100644 index 0000000..8fa3d1f --- /dev/null +++ b/.idea/artifacts/util_jvm_0_2_8.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_jvm_0_3_0_beta_1.xml b/.idea/artifacts/util_jvm_0_3_0_beta_1.xml new file mode 100644 index 0000000..55dfb20 --- /dev/null +++ b/.idea/artifacts/util_jvm_0_3_0_beta_1.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_js_0_2_8.xml b/.idea/artifacts/util_wasm_js_0_2_8.xml new file mode 100644 index 0000000..50d7e15 --- /dev/null +++ b/.idea/artifacts/util_wasm_js_0_2_8.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_js_0_3_0_beta_1.xml b/.idea/artifacts/util_wasm_js_0_3_0_beta_1.xml new file mode 100644 index 0000000..a229bf4 --- /dev/null +++ b/.idea/artifacts/util_wasm_js_0_3_0_beta_1.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_wasi_0_2_8.xml b/.idea/artifacts/util_wasm_wasi_0_2_8.xml new file mode 100644 index 0000000..587e6c4 --- /dev/null +++ b/.idea/artifacts/util_wasm_wasi_0_2_8.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml new file mode 100644 index 0000000..b4fdd03 --- /dev/null +++ b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..bcf4711 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 7c47dd4..c0278ba 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,5 +4,5 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a0f0f48 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/util.jvmMain.iml b/.idea/modules/util.jvmMain.iml new file mode 100644 index 0000000..2c83a31 --- /dev/null +++ b/.idea/modules/util.jvmMain.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 4843d18..ad72d7a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,15 @@ -@file:OptIn(ExperimentalKotlinGradlePluginApi::class) +//@file:OptIn(ExperimentalKotlinGradlePluginApi::class) +@file:OptIn(ExperimentalWasmDsl::class) -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - kotlin("jvm") version masterLibs.versions.kotlinVersion - `java-library` +// kotlin("jvm") version masterLibs.versions.kotlinVersion +// `java-library` `maven-publish` alias(masterLibs.plugins.dokkaPlugin) + alias(masterLibs.plugins.kotlinMultiplatformPlugin) alias(masterLibs.plugins.versionPlugin) } @@ -20,27 +23,98 @@ repositories { maven("https://nexus.keinekekse.net/repository/maven-public/") } -dependencies { - implementation(masterLibs.kotlinxSerialization) - // f. cmd line via annotation KMP? - implementation(kotlin("reflect")) - -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(masterLibs.versions.javaVersion.get())) - } - withSourcesJar() -} +//dependencies { +// implementation(masterLibs.kotlinxSerialization) +// // f. cmd line via annotation KMP? +// implementation(kotlin("reflect")) +// +//} kotlin { - jvmToolchain(masterLibs.versions.javaVersion.get().toInt()) -// compilerOptions { -// languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_3) -// } +// explicitApi() + + jvm { + compilerOptions.jvmTarget = + JvmTarget.valueOf("JVM_" + masterLibs.versions.javaVersion.get()) //JvmTarget.JVM_1_8 + } + + js { + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + nodejs() + } + + wasmJs { + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + nodejs() + } + + wasmWasi { + nodejs() + } + +// macosX64() + macosArm64() + +// iosX64() +// iosArm64() +// iosSimulatorArm64() + +// watchosArm32() +// watchosArm64() +// watchosX64() +// watchosSimulatorArm64() +// watchosDeviceArm64() + +// tvosArm64() +// tvosX64() +// tvosSimulatorArm64() + +// mingwX64() + + linuxX64() +// linuxArm64() + +// androidNativeArm32() +// androidNativeArm64() +// androidNativeX86() +// androidNativeX64() + + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain.dependencies { + implementation(masterLibs.kotlinxSerialization) + } + jvmMain.dependencies { + implementation(kotlin("reflect")) + } + +// commonTest.dependencies { +// implementation(libs.kotlin.test) +// implementation(libs.serialization.json) +// } + } } +//kotlin { +// jvmToolchain(masterLibs.versions.javaVersion.get().toInt()) +//// compilerOptions { +//// languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_3) +//// } +//} + /* Generierte Dokka-Doku als javadoc.jar ausgeben (s.a. "publishing") tasks.register("dokkaJar") { group = "build" @@ -62,10 +136,10 @@ publishing { url = uri("https://nexus.keinekekse.net/repository/maven-releases/") } } - publications.create("maven") { - from(components["java"]) -// artifact(tasks["dokkaJar"]) - } +// publications.create("maven") { +// from(components["java"]) +//// artifact(tasks["dokkaJar"]) +// } } // ./gradlew dokkaGenerate diff --git a/buildnumber.properties b/buildnumber.properties index 4d39833..5efe45f 100644 --- a/buildnumber.properties +++ b/buildnumber.properties @@ -1,3 +1,3 @@ #auto generated -#Sun Jan 18 02:02:24 CET 2026 -build.number=106 +#Sun Jan 18 17:08:09 CET 2026 +build.number=109 diff --git a/settings.gradle.kts b/settings.gradle.kts index ab23bf9..212177d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ dependencyResolutionManagement { versionCatalogs { create("masterLibs") { // from(files("gradle/masterlibs.versions.toml")) - from("net.keinekekse:version-catalog:107") + from("net.keinekekse:version-catalog:109") } } } diff --git a/src/main/kotlin/net/keinekekse/args/priv.kt b/src/commonMain/kotlin/net/keinekekse/args/priv.kt similarity index 97% rename from src/main/kotlin/net/keinekekse/args/priv.kt rename to src/commonMain/kotlin/net/keinekekse/args/priv.kt index 15ec705..2610210 100644 --- a/src/main/kotlin/net/keinekekse/args/priv.kt +++ b/src/commonMain/kotlin/net/keinekekse/args/priv.kt @@ -1,13 +1,14 @@ package net.keinekekse.args import net.keinekekse.version.Version -import kotlin.system.exitProcess + +expect fun exitApplication(rc: Int = 0): Nothing class VerifyParameterException(message: String) : RuntimeException(message) fun showParsingError(msg: String): Nothing { - println(ArgumentParserException.PARSING_ERROR_MSG.format(msg)) - exitProcess(2) + println(ArgumentParserException.PARSING_ERROR_MSG + msg + "\n") + exitApplication(2) } /** @@ -311,10 +312,10 @@ class CommandLine( } e?.message?.let { println() - println(ArgumentParserException.PARSING_ERROR_MSG.format(it)) + println(ArgumentParserException.PARSING_ERROR_MSG + it + "\n") println() } - exitProcess(0) + exitApplication(0) } private fun showSubcommands(parameters: MutableMap) { @@ -346,7 +347,7 @@ class CommandLine( private fun showVersion(): Nothing { println("${programVersion.displayName} ${programVersion.fullVersion}, ${programVersion.copyright}\n") - exitProcess(0) + exitApplication(0) } } diff --git a/src/main/kotlin/net/keinekekse/args/pub.kt b/src/commonMain/kotlin/net/keinekekse/args/pub.kt similarity index 98% rename from src/main/kotlin/net/keinekekse/args/pub.kt rename to src/commonMain/kotlin/net/keinekekse/args/pub.kt index c2d8b70..bf11abc 100644 --- a/src/main/kotlin/net/keinekekse/args/pub.kt +++ b/src/commonMain/kotlin/net/keinekekse/args/pub.kt @@ -1,7 +1,6 @@ package net.keinekekse.args import kotlin.reflect.KProperty -import kotlin.system.exitProcess interface ValueDelegate { var value: T? @@ -152,8 +151,8 @@ interface AddParameter { // Fehler beim Check der Parameter-Definitionen und beim Parsen der Kommandozeile internal class ArgumentParserException(message: String?) : RuntimeException(message) { companion object { - const val PARSING_ERROR_MSG = "\n*** ERROR parsing arguments: %s\n" - const val DEFINITION_ERROR_MSG = "\n*** ERROR defining arguments: %s\n" + const val PARSING_ERROR_MSG = "\n*** ERROR parsing arguments: " + const val DEFINITION_ERROR_MSG = "\n*** ERROR defining arguments: " fun showHelp() = ArgumentParserException(null) fun noHelpOrVersion() = ArgumentParserException("Option 'help' and option 'version' are not allowed.") @@ -226,6 +225,6 @@ internal fun matchArgument(arg: String): Pair? = ARGUMENT_PATTERN.matchEntire(arg)?.groupValues?.let { Pair(it[1] + it[2], it[3]) } internal fun showDefiningError(msg: String): Nothing { - println(ArgumentParserException.DEFINITION_ERROR_MSG.format(msg)) - exitProcess(1) + println(ArgumentParserException.DEFINITION_ERROR_MSG + msg + "\n") + exitApplication(1) } diff --git a/src/main/kotlin/net/keinekekse/util/Version.kt b/src/commonMain/kotlin/net/keinekekse/util/Version.kt similarity index 51% rename from src/main/kotlin/net/keinekekse/util/Version.kt rename to src/commonMain/kotlin/net/keinekekse/util/Version.kt index 30695fb..665f87e 100644 --- a/src/main/kotlin/net/keinekekse/util/Version.kt +++ b/src/commonMain/kotlin/net/keinekekse/util/Version.kt @@ -3,14 +3,14 @@ package net.keinekekse.util import net.keinekekse.version.Version object VERSION: Version { - override val version = "0.2.8" - override val fullVersion = "0.2.8+build.106" + override val version = "0.3.0-beta.1+build.109" + override val fullVersion = "0.3.0-beta.1+build.109" override val major = 0 - override val minor = 2 - override val patch = 8 - override val preRelease = "" - override val buildMetadata = "build.106" - override val buildDate = "2026-01-18T01:02:24.139170Z" + override val minor = 3 + override val patch = 0 + override val preRelease = "beta.1" + override val buildMetadata = "build.109" + override val buildDate = "2026-01-18T16:08:09.445707Z" override val displayName = "Utility Library" override val copyright = "Copyright (c) 2025-2026 Hartmut Bauer" override val name = "util" diff --git a/src/main/kotlin/net/keinekekse/util/delegate.kt b/src/commonMain/kotlin/net/keinekekse/util/delegate.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/util/delegate.kt rename to src/commonMain/kotlin/net/keinekekse/util/delegate.kt diff --git a/src/main/kotlin/net/keinekekse/version/Version.kt b/src/commonMain/kotlin/net/keinekekse/version/Version.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/version/Version.kt rename to src/commonMain/kotlin/net/keinekekse/version/Version.kt diff --git a/src/main/kotlin/net/keinekekse/args/annotation/api.kt b/src/jvmMain/kotlin/net/keinekekse/args/annotation/api.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/args/annotation/api.kt rename to src/jvmMain/kotlin/net/keinekekse/args/annotation/api.kt diff --git a/src/main/kotlin/net/keinekekse/args/annotation/impl.kt b/src/jvmMain/kotlin/net/keinekekse/args/annotation/impl.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/args/annotation/impl.kt rename to src/jvmMain/kotlin/net/keinekekse/args/annotation/impl.kt diff --git a/src/jvmMain/kotlin/net/keinekekse/args/priv.jvm.kt b/src/jvmMain/kotlin/net/keinekekse/args/priv.jvm.kt new file mode 100644 index 0000000..e2687e7 --- /dev/null +++ b/src/jvmMain/kotlin/net/keinekekse/args/priv.jvm.kt @@ -0,0 +1,7 @@ +package net.keinekekse.args + +import kotlin.system.exitProcess + +actual fun exitApplication(rc: Int): Nothing { + exitProcess(rc) +} diff --git a/src/main/kotlin/net/keinekekse/args/v1/arguments.kt b/src/jvmMain/kotlin/net/keinekekse/args/v1/arguments.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/args/v1/arguments.kt rename to src/jvmMain/kotlin/net/keinekekse/args/v1/arguments.kt diff --git a/src/main/kotlin/net/keinekekse/config/init.kt b/src/jvmMain/kotlin/net/keinekekse/config/init.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/config/init.kt rename to src/jvmMain/kotlin/net/keinekekse/config/init.kt diff --git a/src/main/kotlin/net/keinekekse/config/system_config.kt b/src/jvmMain/kotlin/net/keinekekse/config/system_config.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/config/system_config.kt rename to src/jvmMain/kotlin/net/keinekekse/config/system_config.kt diff --git a/src/main/kotlin/net/keinekekse/config/user_config.kt b/src/jvmMain/kotlin/net/keinekekse/config/user_config.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/config/user_config.kt rename to src/jvmMain/kotlin/net/keinekekse/config/user_config.kt diff --git a/src/main/kotlin/net/keinekekse/exec/exec.kt b/src/jvmMain/kotlin/net/keinekekse/exec/exec.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/exec/exec.kt rename to src/jvmMain/kotlin/net/keinekekse/exec/exec.kt diff --git a/src/main/kotlin/net/keinekekse/util/net.kt b/src/jvmMain/kotlin/net/keinekekse/util/net.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/util/net.kt rename to src/jvmMain/kotlin/net/keinekekse/util/net.kt diff --git a/src/main/kotlin/net/keinekekse/version/manifest/version.kt b/src/jvmMain/kotlin/net/keinekekse/version/manifest/version.kt similarity index 100% rename from src/main/kotlin/net/keinekekse/version/manifest/version.kt rename to src/jvmMain/kotlin/net/keinekekse/version/manifest/version.kt diff --git a/src/linuxX64Main/kotlin/net/keinekekse/args/priv.linuxX64.kt b/src/linuxX64Main/kotlin/net/keinekekse/args/priv.linuxX64.kt new file mode 100644 index 0000000..4a5b50e --- /dev/null +++ b/src/linuxX64Main/kotlin/net/keinekekse/args/priv.linuxX64.kt @@ -0,0 +1,8 @@ +package net.keinekekse.args + +import platform.posix.exit + +actual fun exitApplication(rc: Int): Nothing { + exit(rc) + return Unit as Nothing +} diff --git a/src/macosArm64Main/kotlin/net/keinekekse/args/priv.macosArm64.kt b/src/macosArm64Main/kotlin/net/keinekekse/args/priv.macosArm64.kt new file mode 100644 index 0000000..4a5b50e --- /dev/null +++ b/src/macosArm64Main/kotlin/net/keinekekse/args/priv.macosArm64.kt @@ -0,0 +1,8 @@ +package net.keinekekse.args + +import platform.posix.exit + +actual fun exitApplication(rc: Int): Nothing { + exit(rc) + return Unit as Nothing +} diff --git a/src/wasmWasiMain/kotlin/net/keinekekse/args/priv.wasmWasi.kt b/src/wasmWasiMain/kotlin/net/keinekekse/args/priv.wasmWasi.kt new file mode 100644 index 0000000..66a21bd --- /dev/null +++ b/src/wasmWasiMain/kotlin/net/keinekekse/args/priv.wasmWasi.kt @@ -0,0 +1,5 @@ +package net.keinekekse.args + +actual fun exitApplication(rc: Int): Nothing { + TODO("Not implemented on this platform") +} diff --git a/src/webMain/kotlin/net/keinekekse/args/priv.web.kt b/src/webMain/kotlin/net/keinekekse/args/priv.web.kt new file mode 100644 index 0000000..af5033f --- /dev/null +++ b/src/webMain/kotlin/net/keinekekse/args/priv.web.kt @@ -0,0 +1,5 @@ +package net.keinekekse.args + +actual fun exitApplication(rc: Int): Nothing { + TODO("Not yet implemented") // js + wasmJs +} From 9f051d57e2148f5f98ad92072462fc9ca05b5c20 Mon Sep 17 00:00:00 2001 From: Hartmut Bauer Date: Sun, 18 Jan 2026 17:53:10 +0100 Subject: [PATCH 2/8] Umstellung KMP: change KMP-Plugin import --- build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ad72d7a..518c3d3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,11 +5,10 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { -// kotlin("jvm") version masterLibs.versions.kotlinVersion + kotlin("multiplatform") version masterLibs.versions.kotlinVersion // `java-library` `maven-publish` alias(masterLibs.plugins.dokkaPlugin) - alias(masterLibs.plugins.kotlinMultiplatformPlugin) alias(masterLibs.plugins.versionPlugin) } From fdde00b40a876948962baf12d0022ee5499a05ec Mon Sep 17 00:00:00 2001 From: Hartmut Bauer Date: Sun, 18 Jan 2026 17:53:35 +0100 Subject: [PATCH 3/8] fix remaining parameters handling --- src/commonMain/kotlin/net/keinekekse/args/priv.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/net/keinekekse/args/priv.kt b/src/commonMain/kotlin/net/keinekekse/args/priv.kt index 2610210..a8705ec 100644 --- a/src/commonMain/kotlin/net/keinekekse/args/priv.kt +++ b/src/commonMain/kotlin/net/keinekekse/args/priv.kt @@ -196,7 +196,7 @@ class CommandLine( val resultSubCommand = parseSubCommand(remaining) validateMandatoryParameters(resultSubCommand) - if (resultSubCommand.additionalParameters.isNotEmpty() && remainingDescription != null) { + if (resultSubCommand.additionalParameters.isNotEmpty() && remainingDescription == null) { throw ArgumentParserException.noMoreOptions() } From 02db9910ff8dee5d6c160610943811c3bbbb3b68 Mon Sep 17 00:00:00 2001 From: Hartmut Bauer Date: Sun, 18 Jan 2026 22:13:41 +0100 Subject: [PATCH 4/8] fix arguments source file names --- src/commonMain/kotlin/net/keinekekse/args/{priv.kt => impl.kt} | 0 src/commonMain/kotlin/net/keinekekse/args/{pub.kt => internal.kt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/commonMain/kotlin/net/keinekekse/args/{priv.kt => impl.kt} (100%) rename src/commonMain/kotlin/net/keinekekse/args/{pub.kt => internal.kt} (100%) diff --git a/src/commonMain/kotlin/net/keinekekse/args/priv.kt b/src/commonMain/kotlin/net/keinekekse/args/impl.kt similarity index 100% rename from src/commonMain/kotlin/net/keinekekse/args/priv.kt rename to src/commonMain/kotlin/net/keinekekse/args/impl.kt diff --git a/src/commonMain/kotlin/net/keinekekse/args/pub.kt b/src/commonMain/kotlin/net/keinekekse/args/internal.kt similarity index 100% rename from src/commonMain/kotlin/net/keinekekse/args/pub.kt rename to src/commonMain/kotlin/net/keinekekse/args/internal.kt From 9df3e0701dd2002e5cf5fd7300e2771344100107 Mon Sep 17 00:00:00 2001 From: Hartmut Bauer Date: Sun, 18 Jan 2026 22:16:11 +0100 Subject: [PATCH 5/8] build artifacts --- .idea/artifacts/util_js_0_3_0_beta_1.xml | 4 +--- .idea/artifacts/util_js_0_3_0_beta_2.xml | 8 ++++++++ .idea/artifacts/util_jvm_0_3_0_beta_1.xml | 4 +--- .idea/artifacts/util_jvm_0_3_0_beta_2.xml | 8 ++++++++ .idea/artifacts/util_wasm_js_0_3_0_beta_1.xml | 4 +--- .idea/artifacts/util_wasm_js_0_3_0_beta_2.xml | 8 ++++++++ .idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml | 4 +--- .idea/artifacts/util_wasm_wasi_0_3_0_beta_2.xml | 8 ++++++++ .idea/modules.xml | 8 -------- buildnumber.properties | 4 ++-- settings.gradle.kts | 2 +- src/commonMain/kotlin/net/keinekekse/util/Version.kt | 10 +++++----- 12 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 .idea/artifacts/util_js_0_3_0_beta_2.xml create mode 100644 .idea/artifacts/util_jvm_0_3_0_beta_2.xml create mode 100644 .idea/artifacts/util_wasm_js_0_3_0_beta_2.xml create mode 100644 .idea/artifacts/util_wasm_wasi_0_3_0_beta_2.xml delete mode 100644 .idea/modules.xml diff --git a/.idea/artifacts/util_js_0_3_0_beta_1.xml b/.idea/artifacts/util_js_0_3_0_beta_1.xml index 4bbd519..00ae358 100644 --- a/.idea/artifacts/util_js_0_3_0_beta_1.xml +++ b/.idea/artifacts/util_js_0_3_0_beta_1.xml @@ -1,8 +1,6 @@ $PROJECT_DIR$/build/libs - - - + \ No newline at end of file diff --git a/.idea/artifacts/util_js_0_3_0_beta_2.xml b/.idea/artifacts/util_js_0_3_0_beta_2.xml new file mode 100644 index 0000000..5eb4e8f --- /dev/null +++ b/.idea/artifacts/util_js_0_3_0_beta_2.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_jvm_0_3_0_beta_1.xml b/.idea/artifacts/util_jvm_0_3_0_beta_1.xml index 55dfb20..8019946 100644 --- a/.idea/artifacts/util_jvm_0_3_0_beta_1.xml +++ b/.idea/artifacts/util_jvm_0_3_0_beta_1.xml @@ -1,8 +1,6 @@ $PROJECT_DIR$/build/libs - - - + \ No newline at end of file diff --git a/.idea/artifacts/util_jvm_0_3_0_beta_2.xml b/.idea/artifacts/util_jvm_0_3_0_beta_2.xml new file mode 100644 index 0000000..46fbd93 --- /dev/null +++ b/.idea/artifacts/util_jvm_0_3_0_beta_2.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_js_0_3_0_beta_1.xml b/.idea/artifacts/util_wasm_js_0_3_0_beta_1.xml index a229bf4..188f1b2 100644 --- a/.idea/artifacts/util_wasm_js_0_3_0_beta_1.xml +++ b/.idea/artifacts/util_wasm_js_0_3_0_beta_1.xml @@ -1,8 +1,6 @@ $PROJECT_DIR$/build/libs - - - + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_js_0_3_0_beta_2.xml b/.idea/artifacts/util_wasm_js_0_3_0_beta_2.xml new file mode 100644 index 0000000..64917ca --- /dev/null +++ b/.idea/artifacts/util_wasm_js_0_3_0_beta_2.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml index b4fdd03..efdc567 100644 --- a/.idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml +++ b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_1.xml @@ -1,8 +1,6 @@ $PROJECT_DIR$/build/libs - - - + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_wasi_0_3_0_beta_2.xml b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_2.xml new file mode 100644 index 0000000..b2d02c0 --- /dev/null +++ b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_2.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index a0f0f48..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/buildnumber.properties b/buildnumber.properties index 5efe45f..ba32cd2 100644 --- a/buildnumber.properties +++ b/buildnumber.properties @@ -1,3 +1,3 @@ #auto generated -#Sun Jan 18 17:08:09 CET 2026 -build.number=109 +#Sun Jan 18 17:59:47 CET 2026 +build.number=111 diff --git a/settings.gradle.kts b/settings.gradle.kts index 212177d..840bafb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ dependencyResolutionManagement { versionCatalogs { create("masterLibs") { // from(files("gradle/masterlibs.versions.toml")) - from("net.keinekekse:version-catalog:109") + from("net.keinekekse:version-catalog:110") } } } diff --git a/src/commonMain/kotlin/net/keinekekse/util/Version.kt b/src/commonMain/kotlin/net/keinekekse/util/Version.kt index 665f87e..460fd0c 100644 --- a/src/commonMain/kotlin/net/keinekekse/util/Version.kt +++ b/src/commonMain/kotlin/net/keinekekse/util/Version.kt @@ -3,14 +3,14 @@ package net.keinekekse.util import net.keinekekse.version.Version object VERSION: Version { - override val version = "0.3.0-beta.1+build.109" - override val fullVersion = "0.3.0-beta.1+build.109" + override val version = "0.3.0-beta.2+build.111" + override val fullVersion = "0.3.0-beta.2+build.111" override val major = 0 override val minor = 3 override val patch = 0 - override val preRelease = "beta.1" - override val buildMetadata = "build.109" - override val buildDate = "2026-01-18T16:08:09.445707Z" + override val preRelease = "beta.2" + override val buildMetadata = "build.111" + override val buildDate = "2026-01-18T16:59:47.995114Z" override val displayName = "Utility Library" override val copyright = "Copyright (c) 2025-2026 Hartmut Bauer" override val name = "util" From b580e64e76c062ddef1dc0a05406b581ef10e61d Mon Sep 17 00:00:00 2001 From: Hartmut Bauer Date: Mon, 19 Jan 2026 00:31:17 +0100 Subject: [PATCH 6/8] =?UTF-8?q?Zus=C3=A4tzliche=20Kommandozeilen-Parameter?= =?UTF-8?q?=20via=20Pipe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/artifacts/util_js_0_3_0_beta_4.xml | 8 ++++ .idea/artifacts/util_jvm_0_3_0_beta_4.xml | 8 ++++ .idea/artifacts/util_wasm_js_0_3_0_beta_4.xml | 8 ++++ .../artifacts/util_wasm_wasi_0_3_0_beta_4.xml | 8 ++++ buildnumber.properties | 4 +- settings.gradle.kts | 2 +- .../kotlin/net/keinekekse/args/impl.kt | 44 +++++++++++++++++-- .../kotlin/net/keinekekse/util/Version.kt | 10 ++--- .../kotlin/net/keinekekse/args/impl.jvm.kt | 9 ++++ .../kotlin/net/keinekekse/args/priv.jvm.kt | 7 --- .../net/keinekekse/args/priv.linuxX64.kt | 8 ---- .../net/keinekekse/args/priv.macosArm64.kt | 8 ---- .../kotlin/net/keinekekse/args/impl.native.kt | 22 ++++++++++ .../{priv.wasmWasi.kt => impl.wasmWasi.kt} | 4 ++ .../kotlin/net/keinekekse/args/impl.web.kt | 11 +++++ .../kotlin/net/keinekekse/args/priv.web.kt | 5 --- 16 files changed, 127 insertions(+), 39 deletions(-) create mode 100644 .idea/artifacts/util_js_0_3_0_beta_4.xml create mode 100644 .idea/artifacts/util_jvm_0_3_0_beta_4.xml create mode 100644 .idea/artifacts/util_wasm_js_0_3_0_beta_4.xml create mode 100644 .idea/artifacts/util_wasm_wasi_0_3_0_beta_4.xml create mode 100644 src/jvmMain/kotlin/net/keinekekse/args/impl.jvm.kt delete mode 100644 src/jvmMain/kotlin/net/keinekekse/args/priv.jvm.kt delete mode 100644 src/linuxX64Main/kotlin/net/keinekekse/args/priv.linuxX64.kt delete mode 100644 src/macosArm64Main/kotlin/net/keinekekse/args/priv.macosArm64.kt create mode 100644 src/nativeMain/kotlin/net/keinekekse/args/impl.native.kt rename src/wasmWasiMain/kotlin/net/keinekekse/args/{priv.wasmWasi.kt => impl.wasmWasi.kt} (58%) create mode 100644 src/webMain/kotlin/net/keinekekse/args/impl.web.kt delete mode 100644 src/webMain/kotlin/net/keinekekse/args/priv.web.kt diff --git a/.idea/artifacts/util_js_0_3_0_beta_4.xml b/.idea/artifacts/util_js_0_3_0_beta_4.xml new file mode 100644 index 0000000..b612884 --- /dev/null +++ b/.idea/artifacts/util_js_0_3_0_beta_4.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_jvm_0_3_0_beta_4.xml b/.idea/artifacts/util_jvm_0_3_0_beta_4.xml new file mode 100644 index 0000000..e64ce9a --- /dev/null +++ b/.idea/artifacts/util_jvm_0_3_0_beta_4.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_js_0_3_0_beta_4.xml b/.idea/artifacts/util_wasm_js_0_3_0_beta_4.xml new file mode 100644 index 0000000..971f2c1 --- /dev/null +++ b/.idea/artifacts/util_wasm_js_0_3_0_beta_4.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_wasi_0_3_0_beta_4.xml b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_4.xml new file mode 100644 index 0000000..ff662b4 --- /dev/null +++ b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_4.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/buildnumber.properties b/buildnumber.properties index ba32cd2..4e78d91 100644 --- a/buildnumber.properties +++ b/buildnumber.properties @@ -1,3 +1,3 @@ #auto generated -#Sun Jan 18 17:59:47 CET 2026 -build.number=111 +#Sun Jan 18 23:38:13 CET 2026 +build.number=114 diff --git a/settings.gradle.kts b/settings.gradle.kts index 840bafb..066964c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ dependencyResolutionManagement { versionCatalogs { create("masterLibs") { // from(files("gradle/masterlibs.versions.toml")) - from("net.keinekekse:version-catalog:110") + from("net.keinekekse:version-catalog:113") } } } diff --git a/src/commonMain/kotlin/net/keinekekse/args/impl.kt b/src/commonMain/kotlin/net/keinekekse/args/impl.kt index a8705ec..cedcab5 100644 --- a/src/commonMain/kotlin/net/keinekekse/args/impl.kt +++ b/src/commonMain/kotlin/net/keinekekse/args/impl.kt @@ -2,8 +2,28 @@ package net.keinekekse.args import net.keinekekse.version.Version +/** + * Terminates the currently running application with the specified exit code. + * This function does not return as it stops the application process. + * + * Works only for JVM and macOS-/Linux-native targets. + * + * @param rc The exit code to return when the application terminates. Defaults to 0, + * indicating normal termination. + * @return This function does not return as the application is terminated. + */ expect fun exitApplication(rc: Int = 0): Nothing +/** + * Checks if standard input is ready for reading. + * This function is used to determine if there is data available to be read from standard input. + * + * Works only for JVM and macOS-/Linux-native targets. + * + * @return True if standard input is ready for reading, false otherwise. + */ +expect fun isStdInputReady(): Boolean + class VerifyParameterException(message: String) : RuntimeException(message) fun showParsingError(msg: String): Nothing { @@ -196,7 +216,12 @@ class CommandLine( val resultSubCommand = parseSubCommand(remaining) validateMandatoryParameters(resultSubCommand) - if (resultSubCommand.additionalParameters.isNotEmpty() && remainingDescription == null) { + if (remainingDescription != null) { + val additionalFromPipe = getRemainingParametersFromPipe() + if (additionalFromPipe.isNotEmpty()) { + resultSubCommand.additionalParameters += additionalFromPipe + } + } else if (resultSubCommand.additionalParameters.isNotEmpty()) { throw ArgumentParserException.noMoreOptions() } @@ -261,6 +286,19 @@ class CommandLine( } } + private fun getRemainingParametersFromPipe(): List { + if (isStdInputReady()) { + var input: String? + val pipe = mutableListOf() + do { + input = readlnOrNull() + input?.let { pipe.add(it) } + } while (!input.isNullOrEmpty()) + return pipe + } + return emptyList() + } + private fun checkExecutors() { globalSubCommand.obj.checkExecutors() subCommands.values.forEach { it.checkExecutors() } @@ -282,7 +320,7 @@ class CommandLine( val globalParam = globalSubCommand.obj.params.isNotEmpty() val remain = remainingDescription != null - println("${programVersion.displayName} ${programVersion.version}, ${programVersion.copyright}\n") + println("${programVersion.displayName} ${programVersion.version} ${programVersion.copyright}\n") println(programVersion.description) println() println( @@ -346,7 +384,7 @@ class CommandLine( } private fun showVersion(): Nothing { - println("${programVersion.displayName} ${programVersion.fullVersion}, ${programVersion.copyright}\n") + println("${programVersion.displayName} ${programVersion.fullVersion} ${programVersion.copyright}\n") exitApplication(0) } diff --git a/src/commonMain/kotlin/net/keinekekse/util/Version.kt b/src/commonMain/kotlin/net/keinekekse/util/Version.kt index 460fd0c..bb52d5d 100644 --- a/src/commonMain/kotlin/net/keinekekse/util/Version.kt +++ b/src/commonMain/kotlin/net/keinekekse/util/Version.kt @@ -3,14 +3,14 @@ package net.keinekekse.util import net.keinekekse.version.Version object VERSION: Version { - override val version = "0.3.0-beta.2+build.111" - override val fullVersion = "0.3.0-beta.2+build.111" + override val version = "0.3.0-beta.5+build.114" + override val fullVersion = "0.3.0-beta.5+build.114" override val major = 0 override val minor = 3 override val patch = 0 - override val preRelease = "beta.2" - override val buildMetadata = "build.111" - override val buildDate = "2026-01-18T16:59:47.995114Z" + override val preRelease = "beta.5" + override val buildMetadata = "build.114" + override val buildDate = "2026-01-18T22:38:13.309402Z" override val displayName = "Utility Library" override val copyright = "Copyright (c) 2025-2026 Hartmut Bauer" override val name = "util" diff --git a/src/jvmMain/kotlin/net/keinekekse/args/impl.jvm.kt b/src/jvmMain/kotlin/net/keinekekse/args/impl.jvm.kt new file mode 100644 index 0000000..b9dccb4 --- /dev/null +++ b/src/jvmMain/kotlin/net/keinekekse/args/impl.jvm.kt @@ -0,0 +1,9 @@ +package net.keinekekse.args + +import kotlin.system.exitProcess + +actual fun isStdInputReady(): Boolean = + (System.`in`.available() > 0) + +actual fun exitApplication(rc: Int): Nothing = + exitProcess(rc) diff --git a/src/jvmMain/kotlin/net/keinekekse/args/priv.jvm.kt b/src/jvmMain/kotlin/net/keinekekse/args/priv.jvm.kt deleted file mode 100644 index e2687e7..0000000 --- a/src/jvmMain/kotlin/net/keinekekse/args/priv.jvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package net.keinekekse.args - -import kotlin.system.exitProcess - -actual fun exitApplication(rc: Int): Nothing { - exitProcess(rc) -} diff --git a/src/linuxX64Main/kotlin/net/keinekekse/args/priv.linuxX64.kt b/src/linuxX64Main/kotlin/net/keinekekse/args/priv.linuxX64.kt deleted file mode 100644 index 4a5b50e..0000000 --- a/src/linuxX64Main/kotlin/net/keinekekse/args/priv.linuxX64.kt +++ /dev/null @@ -1,8 +0,0 @@ -package net.keinekekse.args - -import platform.posix.exit - -actual fun exitApplication(rc: Int): Nothing { - exit(rc) - return Unit as Nothing -} diff --git a/src/macosArm64Main/kotlin/net/keinekekse/args/priv.macosArm64.kt b/src/macosArm64Main/kotlin/net/keinekekse/args/priv.macosArm64.kt deleted file mode 100644 index 4a5b50e..0000000 --- a/src/macosArm64Main/kotlin/net/keinekekse/args/priv.macosArm64.kt +++ /dev/null @@ -1,8 +0,0 @@ -package net.keinekekse.args - -import platform.posix.exit - -actual fun exitApplication(rc: Int): Nothing { - exit(rc) - return Unit as Nothing -} diff --git a/src/nativeMain/kotlin/net/keinekekse/args/impl.native.kt b/src/nativeMain/kotlin/net/keinekekse/args/impl.native.kt new file mode 100644 index 0000000..aba074d --- /dev/null +++ b/src/nativeMain/kotlin/net/keinekekse/args/impl.native.kt @@ -0,0 +1,22 @@ +package net.keinekekse.args + +import kotlinx.cinterop.* +import platform.posix.* + +// macOS + Linux + +@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) +actual fun isStdInputReady(): Boolean { + return memScoped { + val pfd = alloc() + pfd.fd = STDIN_FILENO + pfd.events = POLLIN.toShort() + // timeout 0 means return immediately + poll(pfd.ptr.reinterpret(), 1u, 10) > 0 + } +} + +actual fun exitApplication(rc: Int): Nothing { + exit(rc) + return Unit as Nothing +} diff --git a/src/wasmWasiMain/kotlin/net/keinekekse/args/priv.wasmWasi.kt b/src/wasmWasiMain/kotlin/net/keinekekse/args/impl.wasmWasi.kt similarity index 58% rename from src/wasmWasiMain/kotlin/net/keinekekse/args/priv.wasmWasi.kt rename to src/wasmWasiMain/kotlin/net/keinekekse/args/impl.wasmWasi.kt index 66a21bd..71e7ed7 100644 --- a/src/wasmWasiMain/kotlin/net/keinekekse/args/priv.wasmWasi.kt +++ b/src/wasmWasiMain/kotlin/net/keinekekse/args/impl.wasmWasi.kt @@ -1,5 +1,9 @@ package net.keinekekse.args +actual fun isStdInputReady(): Boolean { + TODO("Not implemented on this platform") +} + actual fun exitApplication(rc: Int): Nothing { TODO("Not implemented on this platform") } diff --git a/src/webMain/kotlin/net/keinekekse/args/impl.web.kt b/src/webMain/kotlin/net/keinekekse/args/impl.web.kt new file mode 100644 index 0000000..024b93a --- /dev/null +++ b/src/webMain/kotlin/net/keinekekse/args/impl.web.kt @@ -0,0 +1,11 @@ +package net.keinekekse.args + +// js + wasmJs + +actual fun isStdInputReady(): Boolean { + TODO("Not implemented for this platform") +} + +actual fun exitApplication(rc: Int): Nothing { + TODO("Not implemented for this platform") +} diff --git a/src/webMain/kotlin/net/keinekekse/args/priv.web.kt b/src/webMain/kotlin/net/keinekekse/args/priv.web.kt deleted file mode 100644 index af5033f..0000000 --- a/src/webMain/kotlin/net/keinekekse/args/priv.web.kt +++ /dev/null @@ -1,5 +0,0 @@ -package net.keinekekse.args - -actual fun exitApplication(rc: Int): Nothing { - TODO("Not yet implemented") // js + wasmJs -} From 66244db097c101bcff357a623eb1781cd4dc6d29 Mon Sep 17 00:00:00 2001 From: Hartmut Bauer Date: Mon, 19 Jan 2026 01:13:02 +0100 Subject: [PATCH 7/8] =?UTF-8?q?Show-Help-Optik=20verbessert,=20Kommantare?= =?UTF-8?q?=20erg=C3=A4nzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- buildnumber.properties | 4 +- settings.gradle.kts | 2 +- .../kotlin/net/keinekekse/args/impl.kt | 44 ++++++++++++++++--- .../kotlin/net/keinekekse/args/internal.kt | 3 +- .../kotlin/net/keinekekse/util/Version.kt | 10 ++--- 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/buildnumber.properties b/buildnumber.properties index 4e78d91..f8b2234 100644 --- a/buildnumber.properties +++ b/buildnumber.properties @@ -1,3 +1,3 @@ #auto generated -#Sun Jan 18 23:38:13 CET 2026 -build.number=114 +#Mon Jan 19 01:08:01 CET 2026 +build.number=117 diff --git a/settings.gradle.kts b/settings.gradle.kts index 066964c..0850fa1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ dependencyResolutionManagement { versionCatalogs { create("masterLibs") { // from(files("gradle/masterlibs.versions.toml")) - from("net.keinekekse:version-catalog:113") + from("net.keinekekse:version-catalog:116") } } } diff --git a/src/commonMain/kotlin/net/keinekekse/args/impl.kt b/src/commonMain/kotlin/net/keinekekse/args/impl.kt index cedcab5..2f5bd38 100644 --- a/src/commonMain/kotlin/net/keinekekse/args/impl.kt +++ b/src/commonMain/kotlin/net/keinekekse/args/impl.kt @@ -1,5 +1,6 @@ package net.keinekekse.args +import net.keinekekse.util.VERSION import net.keinekekse.version.Version /** @@ -26,9 +27,18 @@ expect fun isStdInputReady(): Boolean class VerifyParameterException(message: String) : RuntimeException(message) +/** + * Throws a [VerifyParameterException] with the specified error message. + * This function is used to signal that there was an error during argument parsing. + * + * For use in the application. The exception will be caught by the command-line parser + * and displayed to the user via showHelp(). + * + * @param msg The error message to include in the exception. + * @throws VerifyParameterException with the provided error message. + */ fun showParsingError(msg: String): Nothing { - println(ArgumentParserException.PARSING_ERROR_MSG + msg + "\n") - exitApplication(2) + throw VerifyParameterException("*** ERROR parsing arguments: $msg") } /** @@ -48,6 +58,18 @@ fun interface RunProgram { fun run(remaining: List) } +/** + * Represents a subcommand that can be added to a command-line argument parser. + * + * A `SubCommand` is used to define and configure subcommands in a CLI application, + * allowing for structured argument parsing and execution of specific logic based + * on the subcommand invoked. It provides mechanisms to add parameters to the + * subcommand, define execution logic, and manage case sensitivity for argument + * names. + * + * @property obj Internal data container for subcommand-specific configuration and parameters. + * @property ignoreCase Defines whether argument names are case-insensitive. Defaults to `false`. + */ class SubCommand private constructor( internal val obj: SubCommandData, val ignoreCase: Boolean = false, @@ -133,6 +155,18 @@ class SubCommand private constructor( } +/** + * Represents a command-line interface for parsing and executing commands, subcommands, and parameters. + * + * This class facilitates defining global parameters and subcommands, parsing provided arguments, + * validating inputs, and executing the corresponding actions. It supports various types of parameters, + * including strings, long integers, and booleans, with both optional and mandatory requirements. + * Additionally, it provides options for ignoring case sensitivity and handling remaining parameters. + * + * @param programVersion The version information of the program, including display name and copyright. + * @param ignoreCase Determines whether the parsing should ignore case sensitivity for commands and parameters. + * @param remainingDescription The description for remaining command-line arguments, or null if no arguments are allowed. + */ class CommandLine( val programVersion: Version, val ignoreCase: Boolean = false, @@ -320,7 +354,7 @@ class CommandLine( val globalParam = globalSubCommand.obj.params.isNotEmpty() val remain = remainingDescription != null - println("${programVersion.displayName} ${programVersion.version} ${programVersion.copyright}\n") + println("\n${programVersion.displayName} ${programVersion.version} ${programVersion.copyright}\n") println(programVersion.description) println() println( @@ -351,8 +385,8 @@ class CommandLine( e?.message?.let { println() println(ArgumentParserException.PARSING_ERROR_MSG + it + "\n") - println() } + println() exitApplication(0) } @@ -384,7 +418,7 @@ class CommandLine( } private fun showVersion(): Nothing { - println("${programVersion.displayName} ${programVersion.fullVersion} ${programVersion.copyright}\n") + println("\n${programVersion.displayName} ${programVersion.fullVersion} ${programVersion.copyright}\n\n(${VERSION.displayName} ${VERSION.fullVersion} ${VERSION.copyright})\n") exitApplication(0) } diff --git a/src/commonMain/kotlin/net/keinekekse/args/internal.kt b/src/commonMain/kotlin/net/keinekekse/args/internal.kt index bf11abc..cc7a879 100644 --- a/src/commonMain/kotlin/net/keinekekse/args/internal.kt +++ b/src/commonMain/kotlin/net/keinekekse/args/internal.kt @@ -152,7 +152,7 @@ interface AddParameter { internal class ArgumentParserException(message: String?) : RuntimeException(message) { companion object { const val PARSING_ERROR_MSG = "\n*** ERROR parsing arguments: " - const val DEFINITION_ERROR_MSG = "\n*** ERROR defining arguments: " + const val DEFINITION_ERROR_MSG = "\n***\n*** ERROR defining arguments in source code: " fun showHelp() = ArgumentParserException(null) fun noHelpOrVersion() = ArgumentParserException("Option 'help' and option 'version' are not allowed.") @@ -224,6 +224,7 @@ internal fun matchSubCommand(cmd: String) = internal fun matchArgument(arg: String): Pair? = ARGUMENT_PATTERN.matchEntire(arg)?.groupValues?.let { Pair(it[1] + it[2], it[3]) } +// Fehler im Quellcode anzeigen internal fun showDefiningError(msg: String): Nothing { println(ArgumentParserException.DEFINITION_ERROR_MSG + msg + "\n") exitApplication(1) diff --git a/src/commonMain/kotlin/net/keinekekse/util/Version.kt b/src/commonMain/kotlin/net/keinekekse/util/Version.kt index bb52d5d..c2465c1 100644 --- a/src/commonMain/kotlin/net/keinekekse/util/Version.kt +++ b/src/commonMain/kotlin/net/keinekekse/util/Version.kt @@ -3,14 +3,14 @@ package net.keinekekse.util import net.keinekekse.version.Version object VERSION: Version { - override val version = "0.3.0-beta.5+build.114" - override val fullVersion = "0.3.0-beta.5+build.114" + override val version = "0.3.0-beta.8+build.117" + override val fullVersion = "0.3.0-beta.8+build.117" override val major = 0 override val minor = 3 override val patch = 0 - override val preRelease = "beta.5" - override val buildMetadata = "build.114" - override val buildDate = "2026-01-18T22:38:13.309402Z" + override val preRelease = "beta.8" + override val buildMetadata = "build.117" + override val buildDate = "2026-01-19T00:08:01.397532Z" override val displayName = "Utility Library" override val copyright = "Copyright (c) 2025-2026 Hartmut Bauer" override val name = "util" From 561fe601a92f7904acc00de2db36110d2923822c Mon Sep 17 00:00:00 2001 From: Hartmut Bauer Date: Mon, 19 Jan 2026 22:13:50 +0100 Subject: [PATCH 8/8] =?UTF-8?q?Refactoring,=20kleine=20Verbesserungen,=20A?= =?UTF-8?q?ufr=C3=A4umarbeiten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 22 +- .idea/artifacts/util_js_0_3_0_beta_11.xml | 8 + .idea/artifacts/util_js_0_3_0_beta_8.xml | 8 + .idea/artifacts/util_jvm_0_3_0_beta_11.xml | 8 + .idea/artifacts/util_jvm_0_3_0_beta_8.xml | 8 + .../artifacts/util_wasm_js_0_3_0_beta_11.xml | 8 + .idea/artifacts/util_wasm_js_0_3_0_beta_8.xml | 8 + .../util_wasm_wasi_0_3_0_beta_11.xml | 8 + .../artifacts/util_wasm_wasi_0_3_0_beta_8.xml | 8 + buildnumber.properties | 4 +- gradle.properties | 7 +- settings.gradle.kts | 2 +- .../kotlin/net/keinekekse/args/impl.kt | 258 +++++++++++------- .../kotlin/net/keinekekse/args/internal.kt | 21 +- .../kotlin/net/keinekekse/util/Version.kt | 10 +- .../net/keinekekse/args/annotation/impl.kt | 9 +- 16 files changed, 255 insertions(+), 142 deletions(-) create mode 100644 .idea/artifacts/util_js_0_3_0_beta_11.xml create mode 100644 .idea/artifacts/util_js_0_3_0_beta_8.xml create mode 100644 .idea/artifacts/util_jvm_0_3_0_beta_11.xml create mode 100644 .idea/artifacts/util_jvm_0_3_0_beta_8.xml create mode 100644 .idea/artifacts/util_wasm_js_0_3_0_beta_11.xml create mode 100644 .idea/artifacts/util_wasm_js_0_3_0_beta_8.xml create mode 100644 .idea/artifacts/util_wasm_wasi_0_3_0_beta_11.xml create mode 100644 .idea/artifacts/util_wasm_wasi_0_3_0_beta_8.xml diff --git a/.gitignore b/.gitignore index 1b6985c..adfa9bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,19 @@ -# Ignore Gradle project-specific cache directory +*.iml +.kotlin .gradle - -# Ignore Gradle build output directory -build +**/build/ +xcuserdata +!src/**/build/ +local.properties +.idea +.DS_Store +captures +.externalNativeBuild +.cxx +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings +node_modules/ diff --git a/.idea/artifacts/util_js_0_3_0_beta_11.xml b/.idea/artifacts/util_js_0_3_0_beta_11.xml new file mode 100644 index 0000000..d84ee32 --- /dev/null +++ b/.idea/artifacts/util_js_0_3_0_beta_11.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_js_0_3_0_beta_8.xml b/.idea/artifacts/util_js_0_3_0_beta_8.xml new file mode 100644 index 0000000..113590e --- /dev/null +++ b/.idea/artifacts/util_js_0_3_0_beta_8.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_jvm_0_3_0_beta_11.xml b/.idea/artifacts/util_jvm_0_3_0_beta_11.xml new file mode 100644 index 0000000..71efc8f --- /dev/null +++ b/.idea/artifacts/util_jvm_0_3_0_beta_11.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_jvm_0_3_0_beta_8.xml b/.idea/artifacts/util_jvm_0_3_0_beta_8.xml new file mode 100644 index 0000000..987a037 --- /dev/null +++ b/.idea/artifacts/util_jvm_0_3_0_beta_8.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_js_0_3_0_beta_11.xml b/.idea/artifacts/util_wasm_js_0_3_0_beta_11.xml new file mode 100644 index 0000000..4ebd95d --- /dev/null +++ b/.idea/artifacts/util_wasm_js_0_3_0_beta_11.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_js_0_3_0_beta_8.xml b/.idea/artifacts/util_wasm_js_0_3_0_beta_8.xml new file mode 100644 index 0000000..7f73424 --- /dev/null +++ b/.idea/artifacts/util_wasm_js_0_3_0_beta_8.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_wasi_0_3_0_beta_11.xml b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_11.xml new file mode 100644 index 0000000..45d90a1 --- /dev/null +++ b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_11.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/util_wasm_wasi_0_3_0_beta_8.xml b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_8.xml new file mode 100644 index 0000000..ccef720 --- /dev/null +++ b/.idea/artifacts/util_wasm_wasi_0_3_0_beta_8.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/buildnumber.properties b/buildnumber.properties index f8b2234..b1b6e94 100644 --- a/buildnumber.properties +++ b/buildnumber.properties @@ -1,3 +1,3 @@ #auto generated -#Mon Jan 19 01:08:01 CET 2026 -build.number=117 +#Mon Jan 19 21:39:57 CET 2026 +build.number=129 diff --git a/gradle.properties b/gradle.properties index 9832217..e594967 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,8 @@ +#Kotlin kotlin.code.style=official -org.gradle.configuration-cache=true +kotlin.daemon.jvmargs=-Xmx3072M org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled +#Gradle +org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 +org.gradle.configuration-cache=true +org.gradle.caching=true diff --git a/settings.gradle.kts b/settings.gradle.kts index 0850fa1..ff040b5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ dependencyResolutionManagement { versionCatalogs { create("masterLibs") { // from(files("gradle/masterlibs.versions.toml")) - from("net.keinekekse:version-catalog:116") + from("net.keinekekse:version-catalog:127") } } } diff --git a/src/commonMain/kotlin/net/keinekekse/args/impl.kt b/src/commonMain/kotlin/net/keinekekse/args/impl.kt index 2f5bd38..52c27c9 100644 --- a/src/commonMain/kotlin/net/keinekekse/args/impl.kt +++ b/src/commonMain/kotlin/net/keinekekse/args/impl.kt @@ -32,13 +32,13 @@ class VerifyParameterException(message: String) : RuntimeException(message) * This function is used to signal that there was an error during argument parsing. * * For use in the application. The exception will be caught by the command-line parser - * and displayed to the user via showHelp(). + * and displayed to the user together with the help information. * * @param msg The error message to include in the exception. * @throws VerifyParameterException with the provided error message. */ fun showParsingError(msg: String): Nothing { - throw VerifyParameterException("*** ERROR parsing arguments: $msg") + throw VerifyParameterException(msg) } /** @@ -117,37 +117,44 @@ class SubCommand private constructor( description: String, isMandatory: Boolean, ): ParameterValue { - val result = runCatching { - val long = long.handleCase(ignoreCase) - val short = short?.toString()?.handleCase(ignoreCase) - type.createOption(long, short, description, isMandatory).apply { - verifyParameter(this) + val processedLong = long.handleCase(ignoreCase) + val processedShort = short?.toString()?.handleCase(ignoreCase) - @Suppress("UNCHECKED_CAST") - obj.params[long] = this as ParameterData - short?.let { - obj.params[it] = this - } + return runCatching { + type.createOption(processedLong, processedShort, description, isMandatory).apply { + verifyParameter(this) + registerParameter(this, processedLong, processedShort) }.prop - } - return result.getOrElse { showDefiningError(it.message ?: "") } + }.getOrElse { showDefiningError(it.message ?: "") } + } + + private fun registerParameter(parameter: ParameterData<*>, long: String, short: String?) { + @Suppress("UNCHECKED_CAST") + val data = parameter as ParameterData + obj.params[long] = data + short?.let { obj.params[it] = data } } private fun verifyParameter(parameter: ParameterData<*>) { - if (!NAME_PATTERN.matches(parameter.long)) throw ArgumentParserException.invalidName( - parameter.long - ) - if (obj.params.containsKey(parameter.long)) throw ArgumentParserException.optionExists( - parameter.long - ) - parameter.short?.let { - if (!SHORT_NAME_PATTERN.matches(it)) { - throw ArgumentParserException.invalidShortName(it) - } - if (obj.params.containsKey(it)) throw ArgumentParserException.optionExists(it) + val longName = parameter.long + if (!NAME_PATTERN.matches(longName)) { + throw ArgumentParserException.invalidName(longName) } - val n = parameter.long.lowercase() - if (n == "help" || n == "version") { + if (obj.params.containsKey(longName)) { + throw ArgumentParserException.optionExists(longName) + } + + parameter.short?.let { shortName -> + if (!SHORT_NAME_PATTERN.matches(shortName)) { + throw ArgumentParserException.invalidShortName(shortName) + } + if (obj.params.containsKey(shortName)) { + throw ArgumentParserException.optionExists(shortName) + } + } + + val lowerLong = longName.lowercase() + if (lowerLong == "help" || lowerLong == "version") { throw ArgumentParserException.noHelpOrVersion() } @@ -166,12 +173,60 @@ class SubCommand private constructor( * @param programVersion The version information of the program, including display name and copyright. * @param ignoreCase Determines whether the parsing should ignore case sensitivity for commands and parameters. * @param remainingDescription The description for remaining command-line arguments, or null if no arguments are allowed. + * @param emptyCommandLineAllowed Indicates whether the parser should allow an empty command line to be parsed. Defaults to false. + * + * ``` + * // global parameters + * + * val commandLine = CommandLine( + * programVersion = VERSION, + * remainingDescription = "EPUB-Dateien", + * ignoreCase = true, + * ) + * + * val param1 by commandLine.addStringParameter( + * long = "param", + * short = 'P', + * description = "parameter description" + * ) + * // more global parameters + * + * commandLine.setExecution { additionalParameters: List -> + * // called when no subcommand is specified + * } + * + * // subcommand + * + * val commandSubCommand = commandLine.addSubCommand( + * "command", + * "My custom command", + * listOf("cmd") // aliases + * ) + * + * val par1 by commandSubCommand.addStringParameter( + * long = "par1", + * description = "Parameter Par1 ", + * isMandatory = true + * ) + * // more parameters + * + * commandSubCommand.setExecution { additionalParameters -> + * // called when this subcommand is found in the command line + * } + * + * // more subcommands + * + * // start program + * commandLine.parseAndRun(*args) + * ``` */ class CommandLine( val programVersion: Version, val ignoreCase: Boolean = false, val remainingDescription: String? = null, // null -> no remaining allowed + val emptyCommandLineAllowed: Boolean = false, ) : AddParameter { + private var hasRun = false private val globalSubCommand = SubCommand.create(SubCommandData(), ignoreCase) private var subCommands = mutableMapOf() @@ -210,68 +265,68 @@ class CommandLine( name: String, description: String, aliases: List = emptyList(), - ): SubCommand { - val result = runCatching { - val name = verifySubCommand(name, ignoreCase) - val subData = SubCommandData(name, description, aliases) - subCommands[name] = subData - aliases.forEach { alias -> - val alias = verifySubCommand(alias, ignoreCase) - subCommands[alias] = subData - } - SubCommand.create(subData, ignoreCase) + ): SubCommand = runCatching { + val normalizedName = normalizeAndVerifySubCommand(name, ignoreCase) + val subData = SubCommandData(normalizedName, description, aliases) + subCommands[normalizedName] = subData + aliases.forEach { alias -> + val alias = normalizeAndVerifySubCommand(alias, ignoreCase) + subCommands[alias] = subData } - return result.getOrElse { showDefiningError(it.message ?: "") } - } + SubCommand.create(subData, ignoreCase) + }.getOrElse { showDefiningError(it.message ?: "") } fun parseAndRun(vararg args: String) { - val result = runCatching { + runCatching { val selectedSubCommand = parse(args.toList()) selectedSubCommand.execute(selectedSubCommand.additionalParameters) + }.onFailure { exception -> + handleErrorAndShowHelp(exception as Exception) } - result.exceptionOrNull()?.let { showHelp(it as Exception) } } - private fun verifySubCommand(cmd: String, ignoreCase: Boolean): String { - val cmd = cmd.handleCase(ignoreCase) - if (!matchSubCommand(cmd)) throw ArgumentParserException.invalidCommand(cmd) - return cmd + private fun normalizeAndVerifySubCommand(cmd: String, ignoreCase: Boolean): String { + val normalized = cmd.handleCase(ignoreCase) + if (!matchSubCommand(normalized)) throw ArgumentParserException.invalidCommand(normalized) + return normalized } private fun parse(args: List): SubCommandData { - if (args.isEmpty()) { - throw ArgumentParserException.commandRequired() - } + if (hasRun) throw ArgumentParserException.noMoreParsing() + hasRun = true + if (args.isEmpty() && !emptyCommandLineAllowed) throw ArgumentParserException.commandRequired() checkExecutors() - val remaining = parseParameters(args, globalSubCommand.obj) + val remainingAfterGlobal = parseParameters(args, globalSubCommand.obj) validateMandatoryParameters(globalSubCommand.obj) - val resultSubCommand = parseSubCommand(remaining) + val resultSubCommand = parseSubCommand(remainingAfterGlobal) validateMandatoryParameters(resultSubCommand) if (remainingDescription != null) { - val additionalFromPipe = getRemainingParametersFromPipe() - if (additionalFromPipe.isNotEmpty()) { - resultSubCommand.additionalParameters += additionalFromPipe - } + resultSubCommand.additionalParameters += resolveRemainingFromPipe() } else if (resultSubCommand.additionalParameters.isNotEmpty()) { throw ArgumentParserException.noMoreOptions() } - return resultSubCommand } + private fun resolveRemainingFromPipe(): List { + return if (isStdInputReady()) getRemainingParametersFromPipe() else emptyList() + } + private fun parseSubCommand(args: List): SubCommandData { - val sub = args.firstOrNull()?.handleCase(ignoreCase)?.let { subCommands[it] } - ?: return globalSubCommand.obj.apply { additionalParameters = args } + val firstArg = args.firstOrNull()?.handleCase(ignoreCase) + val sub = subCommands[firstArg] ?: return globalSubCommand.obj.apply { + additionalParameters = args + } return sub.apply { additionalParameters = parseParameters(args.drop(1), this) } } private fun validateMandatoryParameters(subCommand: SubCommandData) { - subCommand.params.forEach { (_, parameter) -> + subCommand.params.filter { it.key == it.value.long }.forEach { (_, parameter) -> if (parameter.isMandatory && parameter.prop.value == null) { - throw ArgumentParserException.optionRequiresValue(parameter.long) + throw ArgumentParserException.optionRequired(parameter.long) } } } @@ -279,17 +334,23 @@ class CommandLine( private fun parseParameters(args: List, subCommand: SubCommandData): List { var index = 0 while (index < args.size) { - val parseResult = matchArgument(args[index]) ?: return args.drop(index) + val (key, inlineValue) = matchArgument(args[index]) ?: return args.drop(index) - var (key, inlineValue) = parseResult checkForHelpOrVersion(key) - key = key.handleCase(ignoreCase) + val normalizedKey = key.handleCase(ignoreCase) + val param = + subCommand.params[normalizedKey] ?: throw ArgumentParserException.unknownOption( + normalizedKey + ) - val param = subCommand.params[key] ?: throw ArgumentParserException.unknownOption(key) - - val (value, consumedExtra) = resolveParameterValue(args, index, key, inlineValue, param) + val (value, consumedExtra) = resolveParameterValue( + args, + index, + normalizedKey, + inlineValue, + param + ) param.type.convertAndSet(value, param.prop) - index += 1 + consumedExtra } return emptyList() @@ -312,7 +373,9 @@ class CommandLine( inlineValue.isNotBlank() -> inlineValue to 0 // --name --irgendwas ist nicht OK - currentIndex < args.size - 1 && !ARGUMENT_PATTERN.matches(args[currentIndex + 1]) -> { + currentIndex < args.size - 1 + && !ARGUMENT_PATTERN.matches(args[currentIndex + 1]) + && args[currentIndex + 1].isNotBlank() -> { args[currentIndex + 1] to 1 } @@ -322,12 +385,11 @@ class CommandLine( private fun getRemainingParametersFromPipe(): List { if (isStdInputReady()) { - var input: String? val pipe = mutableListOf() - do { - input = readlnOrNull() - input?.let { pipe.add(it) } - } while (!input.isNullOrEmpty()) + while (true) { + val input = readlnOrNull() ?: break + pipe.add(input) + } return pipe } return emptyList() @@ -347,37 +409,28 @@ class CommandLine( } } - private fun showHelp( + private fun handleErrorAndShowHelp( e: Exception?, ): Nothing { - val sub = !subCommands.isEmpty() - val globalParam = globalSubCommand.obj.params.isNotEmpty() - val remain = remainingDescription != null + val hasSubCommands = subCommands.isNotEmpty() + val hasGlobalParams = globalSubCommand.obj.params.isNotEmpty() + val allowsRemaining = remainingDescription != null println("\n${programVersion.displayName} ${programVersion.version} ${programVersion.copyright}\n") - println(programVersion.description) - println() - println( - "usage: ${programVersion.name} ${ - if (sub) - if (globalParam) { - "[GLOBAL PARAMETER]" - } else { - "" - } + " subcommand [PARAMETER]" - else "[PARAMETER]" - } ${if (remain) "<$remainingDescription>" else ""}" - ) - println() - if (globalParam) { - if (sub) { - println("Global Parameters:") - } else { - println("Parameters:") - } + println("${programVersion.description}\n") + + val usagePrefix = "usage: ${programVersion.name}" + val globalPart = + if (hasGlobalParams) "[${if (hasSubCommands) "GLOBAL " else ""}PARAMETER]" else "" + val subPart = if (hasSubCommands) " subcommand [PARAMETER]" else "" + val remainPart = if (allowsRemaining) " <$remainingDescription>" else "" + + println("$usagePrefix $globalPart$subPart$remainPart\n") + if (hasGlobalParams) { + println("${if (hasSubCommands) "Global " else ""}Parameters:") } showParameters(globalSubCommand.obj.params) - if (sub) { + if (hasSubCommands) { println() println("Subcommands:") showSubcommands(subCommands) @@ -400,21 +453,20 @@ class CommandLine( println("$INDENT${param.name}$aliases ${param.description}") showParameters(param.params) println() - } - } private fun showParameters(parameters: MutableMap>) { var len = 0 parameters.filter { it.key == it.value.long }.map { (_, param) -> - len = maxOf(len, param.long.length + INDENT.length * 2 + 4 + 2) - Pair(INDENT + INDENT + (param.short?.let { "-$it, " } ?: " ") + "--${param.long}", - "${param.description} ${if (param.isMandatory) "*" else ""}") + val parmStr = INDENT + INDENT + + (param.short?.let { "-$it, " } ?: " ") + + "--${param.long}" + if (param.type != ParameterType.BOOLEAN) " <...>" else "" + len = maxOf(len, parmStr.length) + Pair(parmStr, "${param.description} ${if (param.isMandatory) "*" else ""}") }.forEach { println(it.first.padEnd(len + 3) + it.second) } - } private fun showVersion(): Nothing { diff --git a/src/commonMain/kotlin/net/keinekekse/args/internal.kt b/src/commonMain/kotlin/net/keinekekse/args/internal.kt index cc7a879..1c96776 100644 --- a/src/commonMain/kotlin/net/keinekekse/args/internal.kt +++ b/src/commonMain/kotlin/net/keinekekse/args/internal.kt @@ -157,42 +157,23 @@ internal class ArgumentParserException(message: String?) : RuntimeException(mess fun noHelpOrVersion() = ArgumentParserException("Option 'help' and option 'version' are not allowed.") - fun noParametersDefined() = ArgumentParserException("No parameters found for command line.") - fun noExecutorDefinedGlobal() = ArgumentParserException("No lambda found for command line.") fun noExecutorDefined(name: String?) = ArgumentParserException("No lambda found for '$name'.") fun optionExists(name: String) = ArgumentParserException("Option '$name' already exists.") - fun invalidAnnotationTargetParameter(name: String) = - ArgumentParserException("Annotation @CmdLineParameter can only be used on mutable properties, '$name' isn't a mutable property.") - - fun notNullableAnnotationTargetParameter(name: String) = - ArgumentParserException("Annotation @CmdLineParameter can only be used on nullable properties, '$name' isn't a nullable property.") - - fun invalidAnnotationTargetOption() = - ArgumentParserException("Annotation @CmdLineOption can only be used on CommandLine Objects.") - - fun optionNameRequired() = ArgumentParserException("Option name is required.") fun unknownOption(name: String) = ArgumentParserException("Unknown option '$name'.") - fun unknownCommand(name: String) = ArgumentParserException("Unknown command '$name'.") fun optionRequiresValue(name: String) = ArgumentParserException("Option '$name' requires a value.") fun optionNoValue(name: String) = ArgumentParserException("Option '$name' does not accept a value.") - fun booleanNotMandatory(name: String) = - ArgumentParserException("Boolean option '$name' can not be mandatory.") - fun optionRequired(name: String) = ArgumentParserException("Option '$name' is required.") fun commandRequired() = ArgumentParserException("At least one command is required.") fun invalidLongValue(value: String) = ArgumentParserException("Value '$value' is not a long integer.") - fun invalidType(name: String, type: String) = - ArgumentParserException("Unsupported parameter type: ${type} for parameter $name") - fun invalidShortName(name: String) = ArgumentParserException("Option short name '$name' is invalid.") @@ -210,7 +191,7 @@ internal const val TRUE = "true" internal const val NAME = """[a-zA-Z][\w-]+""" internal const val SHORT_NAME = """[a-zA-Z]""" -internal val ARGUMENT_PATTERN = """^(?:-(${SHORT_NAME})|--(${NAME}))(?:=(.+)|$)$""".toRegex() +internal val ARGUMENT_PATTERN = """^(?:-(${SHORT_NAME})|--(${NAME}))(?:[=:](.*)|$)$""".toRegex() internal val NAME_PATTERN = """^${NAME}$""".toRegex() internal val SHORT_NAME_PATTERN = """^${SHORT_NAME}$""".toRegex() diff --git a/src/commonMain/kotlin/net/keinekekse/util/Version.kt b/src/commonMain/kotlin/net/keinekekse/util/Version.kt index c2465c1..7ed8f34 100644 --- a/src/commonMain/kotlin/net/keinekekse/util/Version.kt +++ b/src/commonMain/kotlin/net/keinekekse/util/Version.kt @@ -3,14 +3,14 @@ package net.keinekekse.util import net.keinekekse.version.Version object VERSION: Version { - override val version = "0.3.0-beta.8+build.117" - override val fullVersion = "0.3.0-beta.8+build.117" + override val version = "0.3.0-beta.19+build.129" + override val fullVersion = "0.3.0-beta.19+build.129" override val major = 0 override val minor = 3 override val patch = 0 - override val preRelease = "beta.8" - override val buildMetadata = "build.117" - override val buildDate = "2026-01-19T00:08:01.397532Z" + override val preRelease = "beta.19" + override val buildMetadata = "build.129" + override val buildDate = "2026-01-19T20:39:57.564140Z" override val displayName = "Utility Library" override val copyright = "Copyright (c) 2025-2026 Hartmut Bauer" override val name = "util" diff --git a/src/jvmMain/kotlin/net/keinekekse/args/annotation/impl.kt b/src/jvmMain/kotlin/net/keinekekse/args/annotation/impl.kt index 5a784ec..b90c00e 100644 --- a/src/jvmMain/kotlin/net/keinekekse/args/annotation/impl.kt +++ b/src/jvmMain/kotlin/net/keinekekse/args/annotation/impl.kt @@ -386,19 +386,16 @@ private fun resolveParameterValue( args: List, currentIndex: Int, key: String, - inlineValue: String, + parameterValue: String, param: CmdParameter, ): Pair { return when { param.type == ParameterType.BOOLEAN -> { - if (inlineValue.isNotBlank()) throw ArgumentParserException.optionNoValue(key) + if (parameterValue.isNotBlank()) throw ArgumentParserException.optionNoValue(key) TRUE to 0 } - // --name=--irgendwas ist OK - inlineValue.isNotBlank() -> inlineValue to 0 - - // --name --irgendwas ist nicht OK + parameterValue.isNotBlank() -> parameterValue to 0 currentIndex < args.size - 1 && !ARGUMENT_PATTERN.matches(args[currentIndex + 1]) -> { args[currentIndex + 1] to 1 }