Merge branch 'kmp'
This commit is contained in:
commit
09a000239c
51 files changed
+862
-430
No files matched your search
+18
-4
@@ -1,5 +1,19 @@
|
|||||||
# Ignore Gradle project-specific cache directory
|
*.iml
|
||||||
|
.kotlin
|
||||||
.gradle
|
.gradle
|
||||||
|
**/build/
|
||||||
# Ignore Gradle build output directory
|
xcuserdata
|
||||||
build
|
!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/
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-js-0.2.8">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-js-0.2.8.jar">
|
||||||
|
<element id="module-output" name="util.jsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-js-0.3.0-beta.1">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-js-0.3.0-beta.1.jar" />
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-js-0.3.0-beta.11">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-js-0.3.0-beta.11.jar">
|
||||||
|
<element id="module-output" name="util.jsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-js-0.3.0-beta.2">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-js-0.3.0-beta.2.jar">
|
||||||
|
<element id="module-output" name="util.jsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-js-0.3.0-beta.4">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-js-0.3.0-beta.4.jar">
|
||||||
|
<element id="module-output" name="util.jsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-js-0.3.0-beta.8">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-js-0.3.0-beta.8.jar">
|
||||||
|
<element id="module-output" name="util.jsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-jvm-0.2.8">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-jvm-0.2.8.jar">
|
||||||
|
<element id="module-output" name="util.jvmMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-jvm-0.3.0-beta.1">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-jvm-0.3.0-beta.1.jar" />
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-jvm-0.3.0-beta.11">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-jvm-0.3.0-beta.11.jar">
|
||||||
|
<element id="module-output" name="util.jvmMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-jvm-0.3.0-beta.2">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-jvm-0.3.0-beta.2.jar">
|
||||||
|
<element id="module-output" name="util.jvmMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-jvm-0.3.0-beta.4">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-jvm-0.3.0-beta.4.jar">
|
||||||
|
<element id="module-output" name="util.jvmMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-jvm-0.3.0-beta.8">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-jvm-0.3.0-beta.8.jar">
|
||||||
|
<element id="module-output" name="util.jvmMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-js-0.2.8">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-js-0.2.8.jar">
|
||||||
|
<element id="module-output" name="util.wasmJsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-js-0.3.0-beta.1">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-js-0.3.0-beta.1.jar" />
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-js-0.3.0-beta.11">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-js-0.3.0-beta.11.jar">
|
||||||
|
<element id="module-output" name="util.wasmJsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-js-0.3.0-beta.2">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-js-0.3.0-beta.2.jar">
|
||||||
|
<element id="module-output" name="util.wasmJsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-js-0.3.0-beta.4">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-js-0.3.0-beta.4.jar">
|
||||||
|
<element id="module-output" name="util.wasmJsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-js-0.3.0-beta.8">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-js-0.3.0-beta.8.jar">
|
||||||
|
<element id="module-output" name="util.wasmJsMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-wasi-0.2.8">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-wasi-0.2.8.jar">
|
||||||
|
<element id="module-output" name="util.wasmWasiMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-wasi-0.3.0-beta.1">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-wasi-0.3.0-beta.1.jar" />
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-wasi-0.3.0-beta.11">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-wasi-0.3.0-beta.11.jar">
|
||||||
|
<element id="module-output" name="util.wasmWasiMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-wasi-0.3.0-beta.2">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-wasi-0.3.0-beta.2.jar">
|
||||||
|
<element id="module-output" name="util.wasmWasiMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-wasi-0.3.0-beta.4">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-wasi-0.3.0-beta.4.jar">
|
||||||
|
<element id="module-output" name="util.wasmWasiMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ArtifactManager">
|
||||||
|
<artifact type="jar" name="util-wasm-wasi-0.3.0-beta.8">
|
||||||
|
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||||
|
<root id="archive" name="util-wasm-wasi-0.3.0-beta.8.jar">
|
||||||
|
<element id="module-output" name="util.wasmWasiMain" />
|
||||||
|
</root>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<bytecodeTargetLevel target="21" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
+1
-1
@@ -4,5 +4,5 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK" />
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="21" project-jdk-type="JavaSDK" />
|
||||||
</project>
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module version="4">
|
||||||
|
<component name="AdditionalModuleElements">
|
||||||
|
<content url="file://$MODULE_DIR$/../../src/jvmMain" dumb="true">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/../../src/jvmMain/kotlin" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
+98
-25
@@ -1,10 +1,12 @@
|
|||||||
@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 {
|
plugins {
|
||||||
kotlin("jvm") version masterLibs.versions.kotlinVersion
|
kotlin("multiplatform") version masterLibs.versions.kotlinVersion
|
||||||
`java-library`
|
// `java-library`
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
alias(masterLibs.plugins.dokkaPlugin)
|
alias(masterLibs.plugins.dokkaPlugin)
|
||||||
alias(masterLibs.plugins.versionPlugin)
|
alias(masterLibs.plugins.versionPlugin)
|
||||||
@@ -20,27 +22,98 @@ repositories {
|
|||||||
maven("https://nexus.keinekekse.net/repository/maven-public/")
|
maven("https://nexus.keinekekse.net/repository/maven-public/")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
//dependencies {
|
||||||
implementation(masterLibs.kotlinxSerialization)
|
// implementation(masterLibs.kotlinxSerialization)
|
||||||
// f. cmd line via annotation KMP?
|
// // f. cmd line via annotation KMP?
|
||||||
implementation(kotlin("reflect"))
|
// implementation(kotlin("reflect"))
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain {
|
|
||||||
languageVersion.set(JavaLanguageVersion.of(masterLibs.versions.javaVersion.get()))
|
|
||||||
}
|
|
||||||
withSourcesJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(masterLibs.versions.javaVersion.get().toInt())
|
// explicitApi()
|
||||||
// compilerOptions {
|
|
||||||
// languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_3)
|
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")
|
/* Generierte Dokka-Doku als javadoc.jar ausgeben (s.a. "publishing")
|
||||||
tasks.register<Jar>("dokkaJar") {
|
tasks.register<Jar>("dokkaJar") {
|
||||||
group = "build"
|
group = "build"
|
||||||
@@ -62,10 +135,10 @@ publishing {
|
|||||||
url = uri("https://nexus.keinekekse.net/repository/maven-releases/")
|
url = uri("https://nexus.keinekekse.net/repository/maven-releases/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
publications.create<MavenPublication>("maven") {
|
// publications.create<MavenPublication>("maven") {
|
||||||
from(components["java"])
|
// from(components["java"])
|
||||||
// artifact(tasks["dokkaJar"])
|
//// artifact(tasks["dokkaJar"])
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ./gradlew dokkaGenerate
|
// ./gradlew dokkaGenerate
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#auto generated
|
#auto generated
|
||||||
#Sun Jan 18 02:02:24 CET 2026
|
#Mon Jan 19 21:39:57 CET 2026
|
||||||
build.number=106
|
build.number=129
|
||||||
+6
-1
@@ -1,3 +1,8 @@
|
|||||||
|
#Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
org.gradle.configuration-cache=true
|
kotlin.daemon.jvmargs=-Xmx3072M
|
||||||
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
|
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
|
||||||
+1
-1
@@ -19,7 +19,7 @@ dependencyResolutionManagement {
|
|||||||
versionCatalogs {
|
versionCatalogs {
|
||||||
create("masterLibs") {
|
create("masterLibs") {
|
||||||
// from(files("gradle/masterlibs.versions.toml"))
|
// from(files("gradle/masterlibs.versions.toml"))
|
||||||
from("net.keinekekse:version-catalog:107")
|
from("net.keinekekse:version-catalog:127")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
package net.keinekekse.args
|
||||||
|
|
||||||
|
import net.keinekekse.util.VERSION
|
||||||
|
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)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface representing a runnable program execution logic.
|
||||||
|
*
|
||||||
|
* This interface allows defining a lambda function or block of code that
|
||||||
|
* processes a list of remaining arguments. It is intended to be used as
|
||||||
|
* a mechanism to encapsulate the execution logic for commands or subcommands
|
||||||
|
* in a command-line parsing framework.
|
||||||
|
*
|
||||||
|
* The primary use case for this interface is to define execution behavior
|
||||||
|
* for subcommands or global commands, commonly used in CLI applications.
|
||||||
|
*
|
||||||
|
* @constructor A single abstract method is provided to execute the specified logic.
|
||||||
|
*/
|
||||||
|
fun interface RunProgram {
|
||||||
|
fun run(remaining: List<String>)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
) : AddParameter {
|
||||||
|
companion object {
|
||||||
|
internal fun create(obj: SubCommandData, ignoreCase: Boolean = false): SubCommand =
|
||||||
|
SubCommand(obj, ignoreCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addStringParameter(
|
||||||
|
long: String,
|
||||||
|
short: Char?,
|
||||||
|
description: String,
|
||||||
|
isMandatory: Boolean,
|
||||||
|
): ParameterValue<String> {
|
||||||
|
return createParameter(ParameterType.STRING, long, short, description, isMandatory)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addLongParameter(
|
||||||
|
long: String,
|
||||||
|
short: Char?,
|
||||||
|
description: String,
|
||||||
|
isMandatory: Boolean,
|
||||||
|
): ParameterValue<Long> {
|
||||||
|
return createParameter(ParameterType.LONG, long, short, description, isMandatory)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addBooleanParameter(
|
||||||
|
long: String,
|
||||||
|
short: Char?,
|
||||||
|
description: String,
|
||||||
|
isMandatory: Boolean,
|
||||||
|
): ParameterValue<Boolean> {
|
||||||
|
return createParameter(ParameterType.BOOLEAN, long, short, description, isMandatory)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setExecution(run: RunProgram) {
|
||||||
|
obj.setExecution(run)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> createParameter(
|
||||||
|
type: ParameterType<T>,
|
||||||
|
long: String,
|
||||||
|
short: Char?,
|
||||||
|
description: String,
|
||||||
|
isMandatory: Boolean,
|
||||||
|
): ParameterValue<T> {
|
||||||
|
val processedLong = long.handleCase(ignoreCase)
|
||||||
|
val processedShort = short?.toString()?.handleCase(ignoreCase)
|
||||||
|
|
||||||
|
return runCatching {
|
||||||
|
type.createOption(processedLong, processedShort, description, isMandatory).apply {
|
||||||
|
verifyParameter(this)
|
||||||
|
registerParameter(this, processedLong, processedShort)
|
||||||
|
}.prop
|
||||||
|
}.getOrElse { showDefiningError(it.message ?: "") }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerParameter(parameter: ParameterData<*>, long: String, short: String?) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val data = parameter as ParameterData<Any>
|
||||||
|
obj.params[long] = data
|
||||||
|
short?.let { obj.params[it] = data }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyParameter(parameter: ParameterData<*>) {
|
||||||
|
val longName = parameter.long
|
||||||
|
if (!NAME_PATTERN.matches(longName)) {
|
||||||
|
throw ArgumentParserException.invalidName(longName)
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @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<String> ->
|
||||||
|
* // 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<String, SubCommandData>()
|
||||||
|
|
||||||
|
override fun addStringParameter(
|
||||||
|
long: String,
|
||||||
|
short: Char?,
|
||||||
|
description: String,
|
||||||
|
isMandatory: Boolean,
|
||||||
|
): ParameterValue<String> {
|
||||||
|
return globalSubCommand.addStringParameter(long, short, description, isMandatory)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addLongParameter(
|
||||||
|
long: String,
|
||||||
|
short: Char?,
|
||||||
|
description: String,
|
||||||
|
isMandatory: Boolean,
|
||||||
|
): ParameterValue<Long> {
|
||||||
|
return globalSubCommand.addLongParameter(long, short, description, isMandatory)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addBooleanParameter(
|
||||||
|
long: String,
|
||||||
|
short: Char?,
|
||||||
|
description: String,
|
||||||
|
isMandatory: Boolean,
|
||||||
|
): ParameterValue<Boolean> {
|
||||||
|
return globalSubCommand.addBooleanParameter(long, short, description, isMandatory)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setExecution(run: RunProgram) {
|
||||||
|
globalSubCommand.setExecution(run)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addSubCommand(
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
aliases: List<String> = emptyList(),
|
||||||
|
): 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
|
||||||
|
}
|
||||||
|
SubCommand.create(subData, ignoreCase)
|
||||||
|
}.getOrElse { showDefiningError(it.message ?: "") }
|
||||||
|
|
||||||
|
fun parseAndRun(vararg args: String) {
|
||||||
|
runCatching {
|
||||||
|
val selectedSubCommand = parse(args.toList())
|
||||||
|
selectedSubCommand.execute(selectedSubCommand.additionalParameters)
|
||||||
|
}.onFailure { exception ->
|
||||||
|
handleErrorAndShowHelp(exception as Exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String>): SubCommandData {
|
||||||
|
if (hasRun) throw ArgumentParserException.noMoreParsing()
|
||||||
|
hasRun = true
|
||||||
|
if (args.isEmpty() && !emptyCommandLineAllowed) throw ArgumentParserException.commandRequired()
|
||||||
|
checkExecutors()
|
||||||
|
|
||||||
|
val remainingAfterGlobal = parseParameters(args, globalSubCommand.obj)
|
||||||
|
validateMandatoryParameters(globalSubCommand.obj)
|
||||||
|
|
||||||
|
val resultSubCommand = parseSubCommand(remainingAfterGlobal)
|
||||||
|
validateMandatoryParameters(resultSubCommand)
|
||||||
|
|
||||||
|
if (remainingDescription != null) {
|
||||||
|
resultSubCommand.additionalParameters += resolveRemainingFromPipe()
|
||||||
|
} else if (resultSubCommand.additionalParameters.isNotEmpty()) {
|
||||||
|
throw ArgumentParserException.noMoreOptions()
|
||||||
|
}
|
||||||
|
return resultSubCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveRemainingFromPipe(): List<String> {
|
||||||
|
return if (isStdInputReady()) getRemainingParametersFromPipe() else emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseSubCommand(args: List<String>): SubCommandData {
|
||||||
|
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.filter { it.key == it.value.long }.forEach { (_, parameter) ->
|
||||||
|
if (parameter.isMandatory && parameter.prop.value == null) {
|
||||||
|
throw ArgumentParserException.optionRequired(parameter.long)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseParameters(args: List<String>, subCommand: SubCommandData): List<String> {
|
||||||
|
var index = 0
|
||||||
|
while (index < args.size) {
|
||||||
|
val (key, inlineValue) = matchArgument(args[index]) ?: return args.drop(index)
|
||||||
|
|
||||||
|
checkForHelpOrVersion(key)
|
||||||
|
val normalizedKey = key.handleCase(ignoreCase)
|
||||||
|
val param =
|
||||||
|
subCommand.params[normalizedKey] ?: throw ArgumentParserException.unknownOption(
|
||||||
|
normalizedKey
|
||||||
|
)
|
||||||
|
|
||||||
|
val (value, consumedExtra) = resolveParameterValue(
|
||||||
|
args,
|
||||||
|
index,
|
||||||
|
normalizedKey,
|
||||||
|
inlineValue,
|
||||||
|
param
|
||||||
|
)
|
||||||
|
param.type.convertAndSet(value, param.prop)
|
||||||
|
index += 1 + consumedExtra
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveParameterValue(
|
||||||
|
args: List<String>,
|
||||||
|
currentIndex: Int,
|
||||||
|
key: String,
|
||||||
|
inlineValue: String,
|
||||||
|
param: ParameterData<Any>,
|
||||||
|
): Pair<String, Int> {
|
||||||
|
return when {
|
||||||
|
param.type == ParameterType.BOOLEAN -> {
|
||||||
|
if (inlineValue.isNotBlank()) throw ArgumentParserException.optionNoValue(key)
|
||||||
|
TRUE to 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// --name=--irgendwas ist OK
|
||||||
|
inlineValue.isNotBlank() -> inlineValue to 0
|
||||||
|
|
||||||
|
// --name --irgendwas ist nicht OK
|
||||||
|
currentIndex < args.size - 1
|
||||||
|
&& !ARGUMENT_PATTERN.matches(args[currentIndex + 1])
|
||||||
|
&& args[currentIndex + 1].isNotBlank() -> {
|
||||||
|
args[currentIndex + 1] to 1
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw ArgumentParserException.optionRequiresValue(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRemainingParametersFromPipe(): List<String> {
|
||||||
|
if (isStdInputReady()) {
|
||||||
|
val pipe = mutableListOf<String>()
|
||||||
|
while (true) {
|
||||||
|
val input = readlnOrNull() ?: break
|
||||||
|
pipe.add(input)
|
||||||
|
}
|
||||||
|
return pipe
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkExecutors() {
|
||||||
|
globalSubCommand.obj.checkExecutors()
|
||||||
|
subCommands.values.forEach { it.checkExecutors() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkForHelpOrVersion(key: String) {
|
||||||
|
val k = key.lowercase()
|
||||||
|
if (k == "help") {
|
||||||
|
throw ArgumentParserException.showHelp()
|
||||||
|
} else if (k == "version") {
|
||||||
|
showVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleErrorAndShowHelp(
|
||||||
|
e: Exception?,
|
||||||
|
): Nothing {
|
||||||
|
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}\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 (hasSubCommands) {
|
||||||
|
println()
|
||||||
|
println("Subcommands:")
|
||||||
|
showSubcommands(subCommands)
|
||||||
|
}
|
||||||
|
e?.message?.let {
|
||||||
|
println()
|
||||||
|
println(ArgumentParserException.PARSING_ERROR_MSG + it + "\n")
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
exitApplication(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSubcommands(parameters: MutableMap<String, SubCommandData>) {
|
||||||
|
parameters.filter { it.key == it.value.name }.forEach { (_, param) ->
|
||||||
|
val aliases = buildString {
|
||||||
|
param.aliases.forEach { alias ->
|
||||||
|
append(" | $alias")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("$INDENT${param.name}$aliases ${param.description}")
|
||||||
|
showParameters(param.params)
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showParameters(parameters: MutableMap<String, ParameterData<Any>>) {
|
||||||
|
var len = 0
|
||||||
|
parameters.filter { it.key == it.value.long }.map { (_, param) ->
|
||||||
|
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 {
|
||||||
|
println("\n${programVersion.displayName} ${programVersion.fullVersion} ${programVersion.copyright}\n\n(${VERSION.displayName} ${VERSION.fullVersion} ${VERSION.copyright})\n")
|
||||||
|
exitApplication(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+6
-25
@@ -1,7 +1,6 @@
|
|||||||
package net.keinekekse.args
|
package net.keinekekse.args
|
||||||
|
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
interface ValueDelegate<T> {
|
interface ValueDelegate<T> {
|
||||||
var value: T?
|
var value: T?
|
||||||
@@ -152,48 +151,29 @@ interface AddParameter {
|
|||||||
// Fehler beim Check der Parameter-Definitionen und beim Parsen der Kommandozeile
|
// Fehler beim Check der Parameter-Definitionen und beim Parsen der Kommandozeile
|
||||||
internal class ArgumentParserException(message: String?) : RuntimeException(message) {
|
internal class ArgumentParserException(message: String?) : RuntimeException(message) {
|
||||||
companion object {
|
companion object {
|
||||||
const val PARSING_ERROR_MSG = "\n*** ERROR parsing arguments: %s\n"
|
const val PARSING_ERROR_MSG = "\n*** ERROR parsing arguments: "
|
||||||
const val DEFINITION_ERROR_MSG = "\n*** ERROR defining arguments: %s\n"
|
const val DEFINITION_ERROR_MSG = "\n***\n*** ERROR defining arguments in source code: "
|
||||||
fun showHelp() = ArgumentParserException(null)
|
fun showHelp() = ArgumentParserException(null)
|
||||||
fun noHelpOrVersion() =
|
fun noHelpOrVersion() =
|
||||||
ArgumentParserException("Option 'help' and option 'version' are not allowed.")
|
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?) =
|
fun noExecutorDefined(name: String?) =
|
||||||
ArgumentParserException("No lambda found for '$name'.")
|
ArgumentParserException("No lambda found for '$name'.")
|
||||||
|
|
||||||
fun optionExists(name: String) = ArgumentParserException("Option '$name' already exists.")
|
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 unknownOption(name: String) = ArgumentParserException("Unknown option '$name'.")
|
||||||
fun unknownCommand(name: String) = ArgumentParserException("Unknown command '$name'.")
|
|
||||||
fun optionRequiresValue(name: String) =
|
fun optionRequiresValue(name: String) =
|
||||||
ArgumentParserException("Option '$name' requires a value.")
|
ArgumentParserException("Option '$name' requires a value.")
|
||||||
|
|
||||||
fun optionNoValue(name: String) =
|
fun optionNoValue(name: String) =
|
||||||
ArgumentParserException("Option '$name' does not accept a value.")
|
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 optionRequired(name: String) = ArgumentParserException("Option '$name' is required.")
|
||||||
fun commandRequired() = ArgumentParserException("At least one command is required.")
|
fun commandRequired() = ArgumentParserException("At least one command is required.")
|
||||||
fun invalidLongValue(value: String) =
|
fun invalidLongValue(value: String) =
|
||||||
ArgumentParserException("Value '$value' is not a long integer.")
|
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) =
|
fun invalidShortName(name: String) =
|
||||||
ArgumentParserException("Option short name '$name' is invalid.")
|
ArgumentParserException("Option short name '$name' is invalid.")
|
||||||
|
|
||||||
@@ -211,7 +191,7 @@ internal const val TRUE = "true"
|
|||||||
|
|
||||||
internal const val NAME = """[a-zA-Z][\w-]+"""
|
internal const val NAME = """[a-zA-Z][\w-]+"""
|
||||||
internal const val SHORT_NAME = """[a-zA-Z]"""
|
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 NAME_PATTERN = """^${NAME}$""".toRegex()
|
||||||
internal val SHORT_NAME_PATTERN = """^${SHORT_NAME}$""".toRegex()
|
internal val SHORT_NAME_PATTERN = """^${SHORT_NAME}$""".toRegex()
|
||||||
|
|
||||||
@@ -225,7 +205,8 @@ internal fun matchSubCommand(cmd: String) =
|
|||||||
internal fun matchArgument(arg: String): Pair<String, String>? =
|
internal fun matchArgument(arg: String): Pair<String, String>? =
|
||||||
ARGUMENT_PATTERN.matchEntire(arg)?.groupValues?.let { Pair(it[1] + it[2], it[3]) }
|
ARGUMENT_PATTERN.matchEntire(arg)?.groupValues?.let { Pair(it[1] + it[2], it[3]) }
|
||||||
|
|
||||||
|
// Fehler im Quellcode anzeigen
|
||||||
internal fun showDefiningError(msg: String): Nothing {
|
internal fun showDefiningError(msg: String): Nothing {
|
||||||
println(ArgumentParserException.DEFINITION_ERROR_MSG.format(msg))
|
println(ArgumentParserException.DEFINITION_ERROR_MSG + msg + "\n")
|
||||||
exitProcess(1)
|
exitApplication(1)
|
||||||
}
|
}
|
||||||
+7
-7
@@ -3,14 +3,14 @@ package net.keinekekse.util
|
|||||||
import net.keinekekse.version.Version
|
import net.keinekekse.version.Version
|
||||||
|
|
||||||
object VERSION: Version {
|
object VERSION: Version {
|
||||||
override val version = "0.2.8"
|
override val version = "0.3.0-beta.19+build.129"
|
||||||
override val fullVersion = "0.2.8+build.106"
|
override val fullVersion = "0.3.0-beta.19+build.129"
|
||||||
override val major = 0
|
override val major = 0
|
||||||
override val minor = 2
|
override val minor = 3
|
||||||
override val patch = 8
|
override val patch = 0
|
||||||
override val preRelease = ""
|
override val preRelease = "beta.19"
|
||||||
override val buildMetadata = "build.106"
|
override val buildMetadata = "build.129"
|
||||||
override val buildDate = "2026-01-18T01:02:24.139170Z"
|
override val buildDate = "2026-01-19T20:39:57.564140Z"
|
||||||
override val displayName = "Utility Library"
|
override val displayName = "Utility Library"
|
||||||
override val copyright = "Copyright (c) 2025-2026 Hartmut Bauer"
|
override val copyright = "Copyright (c) 2025-2026 Hartmut Bauer"
|
||||||
override val name = "util"
|
override val name = "util"
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+3
-6
@@ -386,19 +386,16 @@ private fun resolveParameterValue(
|
|||||||
args: List<String>,
|
args: List<String>,
|
||||||
currentIndex: Int,
|
currentIndex: Int,
|
||||||
key: String,
|
key: String,
|
||||||
inlineValue: String,
|
parameterValue: String,
|
||||||
param: CmdParameter,
|
param: CmdParameter,
|
||||||
): Pair<String, Int> {
|
): Pair<String, Int> {
|
||||||
return when {
|
return when {
|
||||||
param.type == ParameterType.BOOLEAN -> {
|
param.type == ParameterType.BOOLEAN -> {
|
||||||
if (inlineValue.isNotBlank()) throw ArgumentParserException.optionNoValue(key)
|
if (parameterValue.isNotBlank()) throw ArgumentParserException.optionNoValue(key)
|
||||||
TRUE to 0
|
TRUE to 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// --name=--irgendwas ist OK
|
parameterValue.isNotBlank() -> parameterValue to 0
|
||||||
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] to 1
|
args[currentIndex + 1] to 1
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,352 +0,0 @@
|
|||||||
package net.keinekekse.args
|
|
||||||
|
|
||||||
import net.keinekekse.version.Version
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
class VerifyParameterException(message: String) : RuntimeException(message)
|
|
||||||
|
|
||||||
fun showParsingError(msg: String): Nothing {
|
|
||||||
println(ArgumentParserException.PARSING_ERROR_MSG.format(msg))
|
|
||||||
exitProcess(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functional interface representing a runnable program execution logic.
|
|
||||||
*
|
|
||||||
* This interface allows defining a lambda function or block of code that
|
|
||||||
* processes a list of remaining arguments. It is intended to be used as
|
|
||||||
* a mechanism to encapsulate the execution logic for commands or subcommands
|
|
||||||
* in a command-line parsing framework.
|
|
||||||
*
|
|
||||||
* The primary use case for this interface is to define execution behavior
|
|
||||||
* for subcommands or global commands, commonly used in CLI applications.
|
|
||||||
*
|
|
||||||
* @constructor A single abstract method is provided to execute the specified logic.
|
|
||||||
*/
|
|
||||||
fun interface RunProgram {
|
|
||||||
fun run(remaining: List<String>)
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubCommand private constructor(
|
|
||||||
internal val obj: SubCommandData,
|
|
||||||
val ignoreCase: Boolean = false,
|
|
||||||
) : AddParameter {
|
|
||||||
companion object {
|
|
||||||
internal fun create(obj: SubCommandData, ignoreCase: Boolean = false): SubCommand =
|
|
||||||
SubCommand(obj, ignoreCase)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addStringParameter(
|
|
||||||
long: String,
|
|
||||||
short: Char?,
|
|
||||||
description: String,
|
|
||||||
isMandatory: Boolean,
|
|
||||||
): ParameterValue<String> {
|
|
||||||
return createParameter(ParameterType.STRING, long, short, description, isMandatory)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addLongParameter(
|
|
||||||
long: String,
|
|
||||||
short: Char?,
|
|
||||||
description: String,
|
|
||||||
isMandatory: Boolean,
|
|
||||||
): ParameterValue<Long> {
|
|
||||||
return createParameter(ParameterType.LONG, long, short, description, isMandatory)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addBooleanParameter(
|
|
||||||
long: String,
|
|
||||||
short: Char?,
|
|
||||||
description: String,
|
|
||||||
isMandatory: Boolean,
|
|
||||||
): ParameterValue<Boolean> {
|
|
||||||
return createParameter(ParameterType.BOOLEAN, long, short, description, isMandatory)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setExecution(run: RunProgram) {
|
|
||||||
obj.setExecution(run)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> createParameter(
|
|
||||||
type: ParameterType<T>,
|
|
||||||
long: String,
|
|
||||||
short: Char?,
|
|
||||||
description: String,
|
|
||||||
isMandatory: Boolean,
|
|
||||||
): ParameterValue<T> {
|
|
||||||
val result = runCatching {
|
|
||||||
val long = long.handleCase(ignoreCase)
|
|
||||||
val short = short?.toString()?.handleCase(ignoreCase)
|
|
||||||
type.createOption(long, short, description, isMandatory).apply {
|
|
||||||
verifyParameter(this)
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
obj.params[long] = this as ParameterData<Any>
|
|
||||||
short?.let {
|
|
||||||
obj.params[it] = this
|
|
||||||
}
|
|
||||||
}.prop
|
|
||||||
}
|
|
||||||
return result.getOrElse { showDefiningError(it.message ?: "") }
|
|
||||||
}
|
|
||||||
|
|
||||||
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 n = parameter.long.lowercase()
|
|
||||||
if (n == "help" || n == "version") {
|
|
||||||
throw ArgumentParserException.noHelpOrVersion()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class CommandLine(
|
|
||||||
val programVersion: Version,
|
|
||||||
val ignoreCase: Boolean = false,
|
|
||||||
val remainingDescription: String? = null, // null -> no remaining allowed
|
|
||||||
) : AddParameter {
|
|
||||||
private val globalSubCommand = SubCommand.create(SubCommandData(), ignoreCase)
|
|
||||||
private var subCommands = mutableMapOf<String, SubCommandData>()
|
|
||||||
|
|
||||||
override fun addStringParameter(
|
|
||||||
long: String,
|
|
||||||
short: Char?,
|
|
||||||
description: String,
|
|
||||||
isMandatory: Boolean,
|
|
||||||
): ParameterValue<String> {
|
|
||||||
return globalSubCommand.addStringParameter(long, short, description, isMandatory)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addLongParameter(
|
|
||||||
long: String,
|
|
||||||
short: Char?,
|
|
||||||
description: String,
|
|
||||||
isMandatory: Boolean,
|
|
||||||
): ParameterValue<Long> {
|
|
||||||
return globalSubCommand.addLongParameter(long, short, description, isMandatory)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addBooleanParameter(
|
|
||||||
long: String,
|
|
||||||
short: Char?,
|
|
||||||
description: String,
|
|
||||||
isMandatory: Boolean,
|
|
||||||
): ParameterValue<Boolean> {
|
|
||||||
return globalSubCommand.addBooleanParameter(long, short, description, isMandatory)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setExecution(run: RunProgram) {
|
|
||||||
globalSubCommand.setExecution(run)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addSubCommand(
|
|
||||||
name: String,
|
|
||||||
description: String,
|
|
||||||
aliases: List<String> = 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)
|
|
||||||
}
|
|
||||||
return result.getOrElse { showDefiningError(it.message ?: "") }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseAndRun(vararg args: String) {
|
|
||||||
val result = runCatching {
|
|
||||||
val selectedSubCommand = parse(args.toList())
|
|
||||||
selectedSubCommand.execute(selectedSubCommand.additionalParameters)
|
|
||||||
}
|
|
||||||
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 parse(args: List<String>): SubCommandData {
|
|
||||||
if (args.isEmpty()) {
|
|
||||||
throw ArgumentParserException.commandRequired()
|
|
||||||
}
|
|
||||||
checkExecutors()
|
|
||||||
|
|
||||||
val remaining = parseParameters(args, globalSubCommand.obj)
|
|
||||||
validateMandatoryParameters(globalSubCommand.obj)
|
|
||||||
|
|
||||||
val resultSubCommand = parseSubCommand(remaining)
|
|
||||||
validateMandatoryParameters(resultSubCommand)
|
|
||||||
|
|
||||||
if (resultSubCommand.additionalParameters.isNotEmpty() && remainingDescription != null) {
|
|
||||||
throw ArgumentParserException.noMoreOptions()
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultSubCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseSubCommand(args: List<String>): SubCommandData {
|
|
||||||
val sub = args.firstOrNull()?.handleCase(ignoreCase)?.let { subCommands[it] }
|
|
||||||
?: return globalSubCommand.obj.apply { additionalParameters = args }
|
|
||||||
return sub.apply { additionalParameters = parseParameters(args.drop(1), this) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateMandatoryParameters(subCommand: SubCommandData) {
|
|
||||||
subCommand.params.forEach { (_, parameter) ->
|
|
||||||
if (parameter.isMandatory && parameter.prop.value == null) {
|
|
||||||
throw ArgumentParserException.optionRequiresValue(parameter.long)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseParameters(args: List<String>, subCommand: SubCommandData): List<String> {
|
|
||||||
var index = 0
|
|
||||||
while (index < args.size) {
|
|
||||||
val parseResult = matchArgument(args[index]) ?: return args.drop(index)
|
|
||||||
|
|
||||||
var (key, inlineValue) = parseResult
|
|
||||||
checkForHelpOrVersion(key)
|
|
||||||
key = key.handleCase(ignoreCase)
|
|
||||||
|
|
||||||
val param = subCommand.params[key] ?: throw ArgumentParserException.unknownOption(key)
|
|
||||||
|
|
||||||
val (value, consumedExtra) = resolveParameterValue(args, index, key, inlineValue, param)
|
|
||||||
param.type.convertAndSet(value, param.prop)
|
|
||||||
|
|
||||||
index += 1 + consumedExtra
|
|
||||||
}
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resolveParameterValue(
|
|
||||||
args: List<String>,
|
|
||||||
currentIndex: Int,
|
|
||||||
key: String,
|
|
||||||
inlineValue: String,
|
|
||||||
param: ParameterData<Any>,
|
|
||||||
): Pair<String, Int> {
|
|
||||||
return when {
|
|
||||||
param.type == ParameterType.BOOLEAN -> {
|
|
||||||
if (inlineValue.isNotBlank()) throw ArgumentParserException.optionNoValue(key)
|
|
||||||
TRUE to 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// --name=--irgendwas ist OK
|
|
||||||
inlineValue.isNotBlank() -> inlineValue to 0
|
|
||||||
|
|
||||||
// --name --irgendwas ist nicht OK
|
|
||||||
currentIndex < args.size - 1 && !ARGUMENT_PATTERN.matches(args[currentIndex + 1]) -> {
|
|
||||||
args[currentIndex + 1] to 1
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw ArgumentParserException.optionRequiresValue(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkExecutors() {
|
|
||||||
globalSubCommand.obj.checkExecutors()
|
|
||||||
subCommands.values.forEach { it.checkExecutors() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkForHelpOrVersion(key: String) {
|
|
||||||
val k = key.lowercase()
|
|
||||||
if (k == "help") {
|
|
||||||
throw ArgumentParserException.showHelp()
|
|
||||||
} else if (k == "version") {
|
|
||||||
showVersion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showHelp(
|
|
||||||
e: Exception?,
|
|
||||||
): Nothing {
|
|
||||||
val sub = !subCommands.isEmpty()
|
|
||||||
val globalParam = globalSubCommand.obj.params.isNotEmpty()
|
|
||||||
val remain = remainingDescription != null
|
|
||||||
|
|
||||||
println("${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:")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showParameters(globalSubCommand.obj.params)
|
|
||||||
if (sub) {
|
|
||||||
println()
|
|
||||||
println("Subcommands:")
|
|
||||||
showSubcommands(subCommands)
|
|
||||||
}
|
|
||||||
e?.message?.let {
|
|
||||||
println()
|
|
||||||
println(ArgumentParserException.PARSING_ERROR_MSG.format(it))
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showSubcommands(parameters: MutableMap<String, SubCommandData>) {
|
|
||||||
parameters.filter { it.key == it.value.name }.forEach { (_, param) ->
|
|
||||||
val aliases = buildString {
|
|
||||||
param.aliases.forEach { alias ->
|
|
||||||
append(" | $alias")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println("$INDENT${param.name}$aliases ${param.description}")
|
|
||||||
showParameters(param.params)
|
|
||||||
println()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showParameters(parameters: MutableMap<String, ParameterData<Any>>) {
|
|
||||||
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 ""}")
|
|
||||||
}.forEach {
|
|
||||||
println(it.first.padEnd(len + 3) + it.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showVersion(): Nothing {
|
|
||||||
println("${programVersion.displayName} ${programVersion.fullVersion}, ${programVersion.copyright}\n")
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<pollfd>()
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +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")
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
Reference in new issue
Block a user