Compare commits

..

9 Commits

Author SHA1 Message Date
6f1134a6fb Merge pull request '1.4.1 release' (#10) from develop into main
All checks were successful
/ publish (push) Successful in 3m54s
Reviewed-on: Infendro/cli-kt#10
2026-01-29 16:48:33 +00:00
21769c20c4 enforce exception message 2026-01-18 21:45:55 +01:00
d5a66f6853 bump version to 1.4.1 2026-01-17 16:02:14 +01:00
f55869193e add additional targets 2026-01-17 16:00:36 +01:00
9ad2d2c5e0 rename ci 2026-01-17 15:59:07 +01:00
a5b578a47d split error and exception 2026-01-17 15:57:05 +01:00
dbd9d53050 improve ci 2026-01-13 15:39:50 +01:00
87e6e65809 improve string distance documentation 2026-01-10 15:12:36 +01:00
54972c36b6 refactor package structure 2026-01-10 09:32:55 +01:00
40 changed files with 137 additions and 120 deletions

View File

@@ -1,36 +0,0 @@
on:
push:
branches:
- main
env:
INFENDRO__TOKEN: ${{ secrets.TOKEN }}
jobs:
publish:
runs-on: linux
steps:
- uses: actions/checkout@v4
- uses: actions/cache/restore@v4
with:
key: gradle
path: |
~/.gradle/caches/
~/.gradle/wrapper/
~/.konan/
- uses: actions/setup-java@v4
with:
java-version: '24'
distribution: 'temurin'
- run: ./gradlew publish
- uses: actions/cache/save@v4
with:
key: gradle
path: |
~/.gradle/caches/
~/.gradle/wrapper/
~/.konan/

View File

@@ -0,0 +1,47 @@
on:
push:
branches: [ main ]
jobs:
publish:
runs-on: linux
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: '24'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v4
with:
key: gradle-${{ hashFiles('**/libs.versions.toml') }}
restore-keys: gradle
path: |
~/.gradle/caches/
~/.gradle/wrapper/
- name: Cache Konan
uses: actions/cache@v4
with:
key: konan-${{ hashFiles('**/libs.versions.toml') }}
restore-keys: konan
path: |
~/.konan/
- name: Cache Node
uses: actions/cache@v4
with:
key: node-${{ hashFiles('**/libs.versions.toml') }}
restore-keys: node
path: |
~/.gradle/nodejs
~/.gradle/yarn
- name: Publish
run: ./gradlew publish
env:
INFENDRO__TOKEN: ${{ secrets.TOKEN }}

View File

@@ -2,15 +2,6 @@
`cli-kt` is a Kotlin Multiplatform framework for implementing terminal applications.
## Targets
| Target | Status |
|------------------|---------------|
| JVM | Supported |
| JavaScript | Supported |
| Native (Linux) | Supported |
| Native (Windows) | Not Supported |
## Installation
Add the following to your `build.gradle.kts`.
@@ -21,7 +12,7 @@ repositories {
}
dependencies {
implementation("com.infendro:cli:1.4.0")
implementation("com.infendro:cli:1.4.1")
}
```

View File

@@ -1,8 +1,11 @@
@file:OptIn(ExperimentalWasmDsl::class)
import com.infendro.plugin.infendro
import com.infendro.plugin.token
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
group = "com.infendro"
version = "1.4.0"
version = "1.4.1"
plugins {
alias(libs.plugins.infendro)
@@ -12,10 +15,14 @@ plugins {
kotlin {
jvm()
js {
nodejs()
}
linuxX64()
linuxArm64()
mingwX64()
macosX64()
macosArm64()
js { nodejs() }
wasmJs { nodejs() }
wasmWasi { nodejs() }
repositories {
mavenCentral()

View File

@@ -1,6 +1,6 @@
[versions]
infendro = "1.0.0"
kotlin = "2.2.21"
kotlin = "2.3.0"
[plugins]
infendro = { id = "com.infendro.plugin", version.ref = "infendro" }

View File

@@ -1,7 +1,7 @@
package com.infendro.cli.command.argument
package com.infendro.cli.argument
import com.infendro.cli.error.build.InvalidArgumentName
import com.infendro.cli.error.build.InvalidRange
import com.infendro.cli.exception.InvalidArgumentName
import com.infendro.cli.exception.InvalidRange
import com.infendro.cli.parser.Parser
import com.infendro.cli.util.Regex.ARGUMENT

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.command.argument
package com.infendro.cli.argument
import com.infendro.cli.parser.*

View File

@@ -1,13 +1,13 @@
package com.infendro.cli.command
import com.infendro.cli.Dsl
import com.infendro.cli.command.argument.Argument
import com.infendro.cli.argument.Argument
import com.infendro.cli.command.context.Context
import com.infendro.cli.command.context.ContextParser
import com.infendro.cli.command.help.DefaultHelpRenderer
import com.infendro.cli.command.help.HelpRenderer
import com.infendro.cli.command.option.Option
import com.infendro.cli.error.build.*
import com.infendro.cli.exception.*
import com.infendro.cli.option.Option
import com.infendro.cli.parser.Parser
import com.infendro.cli.util.Regex
@@ -33,7 +33,7 @@ sealed class Command private constructor(
if (help != null) print(help)
}
is ContextParser.Result.Failure -> {
println("error: ${result.failure.message}")
println("error: ${result.error.message}")
val command = result.path.last()
val help = command.renderer.render(result.path)
if (help != null) print("\n$help")

View File

@@ -2,8 +2,8 @@ package com.infendro.cli.command.context
import com.infendro.cli.Dsl
import com.infendro.cli.command.Command
import com.infendro.cli.command.argument.Argument
import com.infendro.cli.command.option.Option
import com.infendro.cli.argument.Argument
import com.infendro.cli.option.Option
import kotlin.reflect.KProperty
@Dsl

View File

@@ -1,7 +1,7 @@
package com.infendro.cli.command.context
import com.infendro.cli.command.Command
import com.infendro.cli.error.run.*
import com.infendro.cli.error.*
import com.infendro.cli.parser.Parser
internal class ContextParser(
@@ -19,13 +19,13 @@ internal class ContextParser(
data class Failure(
val path: List<Command>,
val failure: RuntimeFailure,
val error: Error,
) : Result()
}
private fun success(context: Context) = Result.Success(context)
private fun help(path: List<Command>) = Result.Help(path)
private fun failure(path: List<Command>, exception: RuntimeFailure) = Result.Failure(path, exception)
private fun failure(path: List<Command>, error: Error) = Result.Failure(path, error)
private var index = 0
private val current: String
@@ -63,7 +63,7 @@ internal class ContextParser(
if (cmd is Command.Fallback<*>) {
val value = when (val result = cmd.parser.parse(current)) {
is Parser.Result.Success<*> -> result.value
is Parser.Result.Failure<*> -> return failure(path, result.failure)
is Parser.Result.Failure<*> -> return failure(path, result.error)
}
commands[cmd.key] = value
}
@@ -91,7 +91,7 @@ internal class ContextParser(
val value = when (val result = argument.parser.parse(current)) {
is Parser.Result.Success<*> -> result.value
is Parser.Result.Failure<*> -> return failure(path, result.failure)
is Parser.Result.Failure<*> -> return failure(path, result.error)
}
arguments[argument]!! += value
argumentCount++
@@ -141,7 +141,7 @@ internal class ContextParser(
text != null ->
when (val result = option.parser.parse(text)) {
is Parser.Result.Success<*> -> result.value
is Parser.Result.Failure<*> -> return failure(path, result.failure)
is Parser.Result.Failure<*> -> return failure(path, result.error)
}
option.flag -> option.fallback!!
else -> return failure(path, MissingOptionValue(option))

View File

@@ -1,8 +1,8 @@
package com.infendro.cli.command.help
import com.infendro.cli.command.Command
import com.infendro.cli.command.argument.Argument
import com.infendro.cli.command.option.Option
import com.infendro.cli.argument.Argument
import com.infendro.cli.option.Option
import kotlin.reflect.KClass
object DefaultHelpRenderer : HelpRenderer {

View File

@@ -0,0 +1,5 @@
package com.infendro.cli.error
abstract class Error {
abstract val message: String
}

View File

@@ -1,11 +1,11 @@
package com.infendro.cli.error.run
package com.infendro.cli.error
import com.infendro.cli.command.argument.Argument
import com.infendro.cli.argument.Argument
class InvalidArgumentCount(
val argument: Argument<*>,
val count: Int,
) : RuntimeFailure() {
) : Error() {
override val message: String
get() {
val expected = when {

View File

@@ -1,11 +1,11 @@
package com.infendro.cli.error.run
package com.infendro.cli.error
import com.infendro.cli.command.option.Option
import com.infendro.cli.option.Option
class InvalidOptionCount(
val option: Option<*>,
val count: Int,
) : RuntimeFailure() {
) : Error() {
override val message: String
get() {
val expected = when {

View File

@@ -1,11 +1,11 @@
package com.infendro.cli.error.run
package com.infendro.cli.error
import kotlin.reflect.KClass
class InvalidValue(
val value: String,
val type: KClass<*>,
) : RuntimeFailure() {
) : Error() {
override val message: String
get() = """"$value" cannot be converted to ${type.simpleName ?: "Unknown"}."""
}

View File

@@ -1,8 +1,8 @@
package com.infendro.cli.error.run
package com.infendro.cli.error
class MalformedOption(
val option: String,
) : RuntimeFailure() {
) : Error() {
override val message: String
get() = """Malformed option "$option"."""
}

View File

@@ -1,10 +1,10 @@
package com.infendro.cli.error.run
package com.infendro.cli.error
import com.infendro.cli.command.option.Option
import com.infendro.cli.option.Option
class MissingOptionValue(
val option: Option<*>,
) : RuntimeFailure() {
) : Error() {
override val message: String
get() = """No value provided for option "${option.name}"."""
}

View File

@@ -1,8 +1,8 @@
package com.infendro.cli.error.run
package com.infendro.cli.error
class UnexpectedArgument(
val argument: String,
) : RuntimeFailure() {
) : Error() {
override val message: String
get() = """Unexpected argument "$argument"."""
}

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.run
package com.infendro.cli.error
import com.infendro.cli.command.Command
import com.infendro.cli.util.distance
@@ -8,7 +8,7 @@ import kotlin.math.min
class UnknownCommand(
val command: Command,
val name: String,
) : RuntimeFailure() {
) : Error() {
override val message: String
get() = buildString {
val suggestion = command.commands

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.run
package com.infendro.cli.error
import com.infendro.cli.command.Command
import com.infendro.cli.util.distance
@@ -8,7 +8,7 @@ import kotlin.math.min
class UnknownOption(
val command: Command,
val name: String,
) : RuntimeFailure() {
) : Error() {
override val message: String
get() = buildString {
val suggestion = command.options

View File

@@ -1,3 +0,0 @@
package com.infendro.cli.error.build
abstract class BuildException : Exception()

View File

@@ -1,5 +0,0 @@
package com.infendro.cli.error.run
abstract class RuntimeFailure {
abstract val message: String
}

View File

@@ -0,0 +1,5 @@
package com.infendro.cli.exception
abstract class BuildException : Exception() {
abstract override val message: String
}

View File

@@ -1,6 +1,6 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
import com.infendro.cli.command.argument.Argument
import com.infendro.cli.argument.Argument
class DuplicateArgument(
val argument: Argument<*>,

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
import com.infendro.cli.command.Command

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class DuplicateExecute : BuildException() {
override val message: String

View File

@@ -1,6 +1,6 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
import com.infendro.cli.command.option.Option
import com.infendro.cli.option.Option
class DuplicateOption(
val option: Option<*>,

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class DuplicateRenderer : BuildException() {
override val message: String

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class InvalidArgumentName(
val name: String,

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class InvalidArgumentOrder : BuildException() {
override val message: String

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class InvalidCommandName(
val name: String,

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class InvalidCommandOrder : BuildException() {
override val message: String

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class InvalidFallback : BuildException() {
override val message: String

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class InvalidOptionName(
val name: String,

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class InvalidRange : BuildException() {
override val message: String

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.error.build
package com.infendro.cli.exception
class MissingOptionName : BuildException() {
override val message: String

View File

@@ -1,8 +1,8 @@
package com.infendro.cli.command.option
package com.infendro.cli.option
import com.infendro.cli.error.build.InvalidOptionName
import com.infendro.cli.error.build.InvalidRange
import com.infendro.cli.error.build.MissingOptionName
import com.infendro.cli.exception.InvalidOptionName
import com.infendro.cli.exception.InvalidRange
import com.infendro.cli.exception.MissingOptionName
import com.infendro.cli.parser.Parser
import com.infendro.cli.util.Regex.OPTION

View File

@@ -1,4 +1,4 @@
package com.infendro.cli.command.option
package com.infendro.cli.option
import com.infendro.cli.parser.*

View File

@@ -1,7 +1,7 @@
package com.infendro.cli.parser
import com.infendro.cli.error.run.InvalidValue
import com.infendro.cli.error.run.RuntimeFailure
import com.infendro.cli.error.Error
import com.infendro.cli.error.InvalidValue
import kotlin.reflect.KClass
abstract class Parser<T : Any>(
@@ -13,7 +13,7 @@ abstract class Parser<T : Any>(
) : Result<T>()
data class Failure<T : Any>(
val failure: RuntimeFailure,
val error: Error,
) : Result<T>()
}

View File

@@ -3,7 +3,13 @@ package com.infendro.cli.util
internal const val threshold = 2
/**
* Damerau-Levenshtein string distance
* Calculates the Damerau-Levenshtein distance between two strings.
*
* The distance is the minimum number of operations (insertion, deletion, substitution, and transposition) required to transform one string into another.
*
* @param a The source string.
* @param b The target string.
* @return The minimum number of operations to transform [a] into [b].
*/
internal fun distance(a: String, b: String): Int {
if (a == b) return 0