improve command handling, change argument/option registration

This commit is contained in:
2026-01-08 18:53:54 +01:00
parent 2a9f086c9d
commit 6aa6c1b3c8
9 changed files with 123 additions and 117 deletions

View File

@@ -8,15 +8,18 @@ import kotlin.reflect.KProperty
@Dsl
class Context internal constructor(
val command: Command,
private val path: List<Command>,
private val commands: Map<Command.Fallback.Key<*>, Any>,
private val arguments: Map<Argument<*>, List<Any>>,
private val options: Map<Option<*>, List<Any>>,
) {
val command: Command
get() = path.last()
internal fun execute() = (command.execute)()
fun help() {
command.help?.let { print(it) }
command.renderer.render(path)?.let { print(it) }
}
@Suppress("UNCHECKED_CAST")

View File

@@ -14,18 +14,18 @@ internal class ContextParser(
) : Result()
data class Help(
val command: Command,
val path: List<Command>,
) : Result()
data class Failure(
val command: Command,
val path: List<Command>,
val error: RunError,
) : Result()
}
private fun success(context: Context) = Result.Success(context)
private fun help(command: Command) = Result.Help(command)
private fun failure(command: Command, exception: RunError) = Result.Failure(command, exception)
private fun help(path: List<Command>) = Result.Help(path)
private fun failure(path: List<Command>, exception: RunError) = Result.Failure(path, exception)
private var index = 0
private val current: String
@@ -41,6 +41,7 @@ internal class ContextParser(
fun parse(): Result {
// command
var command = root
val path = mutableListOf(root)
val commands = mutableMapOf<Command.Fallback.Key<*>, Any>()
while (hasNext()) {
@@ -52,16 +53,17 @@ internal class ContextParser(
}
if (cmd == null) {
when {
command.arguments.isEmpty() -> return failure(command, UnknownCommand(current))
command.arguments.isEmpty() -> return failure(path, UnknownCommand(current))
else -> break
}
}
command = cmd
path += cmd
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(command, result.error)
is Parser.Result.Failure<*> -> return failure(path, result.error)
}
commands[cmd.key] = value
}
@@ -85,11 +87,11 @@ internal class ContextParser(
// argument
endOfOptions || dashes == 0 -> {
val argument = command.arguments.getOrNull(argumentIndex)
?: return failure(command, UnexpectedArgument())
?: return failure(path, UnexpectedArgument())
val value = when (val result = argument.parser.parse(trimmed)) {
is Parser.Result.Success<*> -> result.value
is Parser.Result.Failure<*> -> return failure(command, result.error)
is Parser.Result.Failure<*> -> return failure(path, result.error)
}
arguments[argument]!! += value
argumentCount++
@@ -109,7 +111,7 @@ internal class ContextParser(
}
if (dashes == 2 && trimmed == "help")
return help(command)
return help(path)
val (name, text) = when {
trimmed.contains('=') -> {
@@ -133,16 +135,16 @@ internal class ContextParser(
for (name in names) {
val option = command.options.firstOrNull { name in it.names }
?: return failure(command, UnknownOption(name))
?: return failure(path, UnknownOption(name))
val value = when {
text != null ->
when (val result = option.parser.parse(current)) {
is Parser.Result.Success<*> -> result.value
is Parser.Result.Failure<*> -> return failure(command, result.error)
is Parser.Result.Failure<*> -> return failure(path, result.error)
}
option.flag -> option.fallback!!
else -> return failure(command, MissingOptionValue(option))
else -> return failure(path, MissingOptionValue(option))
}
options[option]!! += value
}
@@ -153,9 +155,9 @@ internal class ContextParser(
when {
current.contains('=') -> {
val (option) = current.split('=', limit = 2)
return failure(command, MalformedOption(option))
return failure(path, MalformedOption(option))
}
else -> return failure(command, MalformedOption(current))
else -> return failure(path, MalformedOption(current))
}
}
}
@@ -166,16 +168,16 @@ internal class ContextParser(
// validate arguments
for (argument in command.arguments) {
val count = arguments[argument]!!.count()
if (count !in argument.min..argument.count) return failure(command, InvalidArgumentCount(argument, count))
if (count !in argument.min..argument.count) return failure(path, InvalidArgumentCount(argument, count))
}
// validate options
for (option in command.options) {
val count = options[option]!!.count()
if (count !in option.min..option.count) return failure(command, InvalidOptionCount(option, count))
if (count !in option.min..option.count) return failure(path, InvalidOptionCount(option, count))
}
val context = Context(command, commands, arguments, options)
val context = Context(path, commands, arguments, options)
return success(context)
}
}