implement automatic help

This commit is contained in:
2026-01-07 18:53:35 +01:00
parent 4e9b358ef8
commit ecd255479a
18 changed files with 277 additions and 88 deletions

View File

@@ -3,25 +3,42 @@ package com.infendro.cli.command
import com.infendro.cli.Dsl
import com.infendro.cli.command.context.Context
import com.infendro.cli.command.context.Parser
import com.infendro.cli.command.help.DefaultHelpRenderer
import com.infendro.cli.command.help.HelpRenderer
import com.infendro.cli.exception.build.*
import com.infendro.cli.exception.run.RunException
import com.infendro.cli.util.Regex.ARGUMENT
import com.infendro.cli.util.Regex.COMMAND
import com.infendro.cli.util.Regex.OPTION
import kotlin.reflect.KProperty
class Command private constructor(
val parent: Command?,
val name: String,
val fallback: Boolean,
val commands: List<Command>,
val arguments: List<Argument<*>>,
val options: List<Option<*>>,
internal val execute: Context.() -> Unit,
internal val renderer: HelpRenderer,
) {
fun run(args: Array<String>) = try {
Parser.parse(this, args).execute()
} catch (e: RunException) {
println(e.message)
val path: List<Command>
get() = when {
parent == null -> listOf(this)
else -> parent.path + this
}
val help: String?
get() = renderer.render(this)
fun run(args: Array<String>) {
when (val result = Parser(this, args).parse()) {
is Parser.Result.Success -> result.context.execute()
is Parser.Result.Help -> result.command.help?.let { println(it) }
is Parser.Result.Failure -> {
println("error: ${result.exception.message}")
result.command.help?.let { println("\n$it") }
}
}
}
@Dsl
@@ -29,8 +46,10 @@ class Command private constructor(
private val level: Int,
private val name: String,
private val fallback: Boolean,
var renderer: HelpRenderer,
) {
private val commands = mutableListOf<Command>()
private var parent: Command? = null
private val builders = mutableListOf<Builder>()
private val arguments = mutableListOf<Argument<*>>()
private val options = mutableListOf<Option<*>>()
private lateinit var _execute: Context.() -> Unit
@@ -40,12 +59,12 @@ class Command private constructor(
fun command(name: String, block: Builder.() -> Unit) {
validateCommand(name)
commands += Builder(level + 1, name, fallback = false).apply(block).build()
builders += Builder(level + 1, name, fallback = false, renderer).apply(block)
}
fun fallback(name: String, block: Builder.(Key) -> Unit) {
validateCommand(name)
commands += Builder(level + 1, name, fallback = true).apply { block(key) }.build()
builders += Builder(level + 1, name, fallback = true, renderer).apply { block(key) }
}
fun argument(argument: Argument<*>) {
@@ -78,19 +97,24 @@ class Command private constructor(
_execute = {}
validate()
return Command(name, fallback, commands, arguments, options, _execute)
val commands = mutableListOf<Command>()
val command = Command(parent, name, fallback, commands, arguments, options, _execute, renderer)
for (builder in builders) {
builder.parent = command
commands += builder.build()
}
return command
}
private fun validateCommand(name: String) {
if (commands.isNotEmpty()) {
val last = commands.last()
when {
last.fallback -> throw InvalidCommandOrder()
}
if (builders.isNotEmpty()) {
val last = builders.last()
if (last.fallback) throw InvalidCommandOrder()
}
when {
!name.matches(COMMAND) -> throw InvalidCommand(name)
commands.any { it.name == name } -> throw DuplicateCommand(name)
builders.any { it.name == name } -> throw DuplicateCommand(name)
}
}
@@ -102,9 +126,7 @@ class Command private constructor(
argument.required && last.optional -> throw InvalidArgumentOrder()
}
}
when {
!argument.name.matches(ARGUMENT) -> throw InvalidArgument(argument.name)
}
if (!argument.name.matches(ARGUMENT)) throw InvalidArgument(argument.name)
}
private fun validateOption(option: Option<*>) {
@@ -117,14 +139,11 @@ class Command private constructor(
}
private fun validateExecute() {
if (::_execute.isInitialized)
throw DuplicateExecute()
if (::_execute.isInitialized) throw DuplicateExecute()
}
private fun validate() {
if (commands.any { it.fallback } && arguments.isNotEmpty()) {
throw InvalidFallback()
}
if (builders.any { it.fallback } && arguments.isNotEmpty()) throw InvalidFallback()
}
}
@@ -134,4 +153,4 @@ class Command private constructor(
}
fun cli(name: String, block: Command.Builder.() -> Unit) =
Command.Builder(-1, name, fallback = false).apply(block).build()
Command.Builder(-1, name, fallback = false, DefaultHelpRenderer).apply(block).build()