185 lines
6.1 KiB
Kotlin
185 lines
6.1 KiB
Kotlin
package com.infendro.cli.command
|
|
|
|
import com.infendro.cli.Dsl
|
|
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.option.Option
|
|
import com.infendro.cli.error.build.*
|
|
import com.infendro.cli.parser.Parser
|
|
import com.infendro.cli.util.Regex
|
|
|
|
sealed class Command private constructor(
|
|
val name: String,
|
|
val description: String?,
|
|
val commands: List<Command>,
|
|
val arguments: List<Argument<*>>,
|
|
val options: List<Option<*>>,
|
|
internal val execute: Context.() -> Unit,
|
|
internal val renderer: HelpRenderer,
|
|
) {
|
|
init {
|
|
if (!name.matches(Regex.COMMAND)) throw InvalidCommandName(name)
|
|
}
|
|
|
|
fun run(vararg args: String) {
|
|
when (val result = ContextParser(this, arrayOf(*args)).parse()) {
|
|
is ContextParser.Result.Success -> result.context.execute()
|
|
is ContextParser.Result.Help -> {
|
|
val command = result.path.last()
|
|
val help = command.renderer.render(result.path)
|
|
if (help != null) print(help)
|
|
}
|
|
is ContextParser.Result.Failure -> {
|
|
println("error: ${result.failure.message}")
|
|
val command = result.path.last()
|
|
val help = command.renderer.render(result.path)
|
|
if (help != null) print("\n$help")
|
|
}
|
|
}
|
|
}
|
|
|
|
class Named(
|
|
name: String,
|
|
description: String?,
|
|
commands: List<Command>,
|
|
arguments: List<Argument<*>>,
|
|
options: List<Option<*>>,
|
|
execute: Context.() -> Unit,
|
|
renderer: HelpRenderer,
|
|
) : Command(name, description, commands, arguments, options, execute, renderer) {
|
|
class Builder(
|
|
name: String,
|
|
description: String?,
|
|
) : Command.Builder<Named>(name, description) {
|
|
override fun create() =
|
|
Named(name, description, commands, arguments, options, execute, renderer)
|
|
}
|
|
}
|
|
|
|
class Fallback<T : Any>(
|
|
val key: Key<T>,
|
|
val parser: Parser<T>,
|
|
name: String,
|
|
description: String?,
|
|
commands: List<Command>,
|
|
arguments: List<Argument<*>>,
|
|
options: List<Option<*>>,
|
|
execute: Context.() -> Unit,
|
|
renderer: HelpRenderer,
|
|
) : Command(name, description, commands, arguments, options, execute, renderer) {
|
|
class Builder<T : Any>(
|
|
val parser: Parser<T>,
|
|
name: String,
|
|
description: String?,
|
|
) : Command.Builder<Fallback<T>>(name, description) {
|
|
internal val key = Key<T>()
|
|
|
|
override fun create() =
|
|
Fallback(key, parser, name, description, commands, arguments, options, execute, renderer)
|
|
}
|
|
|
|
class Key<T>
|
|
}
|
|
|
|
@Dsl
|
|
sealed class Builder<COMMAND : Command>(
|
|
protected val name: String,
|
|
protected val description: String?,
|
|
) {
|
|
protected val commands = mutableListOf<Command>()
|
|
protected val arguments = mutableListOf<Argument<*>>()
|
|
protected val options = mutableListOf<Option<*>>()
|
|
private var _execute: (Context.() -> Unit)? = null
|
|
private var _renderer: HelpRenderer? = null
|
|
|
|
protected val execute: Context.() -> Unit
|
|
get() = _execute!!
|
|
protected val renderer: HelpRenderer
|
|
get() = _renderer!!
|
|
|
|
fun register(command: Command) {
|
|
validateCommand(command)
|
|
commands += command
|
|
}
|
|
|
|
fun register(vararg commands: Command) = commands.forEach(::register)
|
|
operator fun <T : Command> T.unaryPlus() = also(::register)
|
|
|
|
fun register(argument: Argument<*>) {
|
|
validateArgument(argument)
|
|
arguments += argument
|
|
}
|
|
|
|
fun register(vararg arguments: Argument<*>) = arguments.forEach(::register)
|
|
operator fun <T : Argument<*>> T.unaryPlus() = also(::register)
|
|
|
|
fun register(option: Option<*>) {
|
|
validateOption(option)
|
|
options += option
|
|
}
|
|
|
|
fun register(vararg options: Option<*>) = options.forEach(::register)
|
|
operator fun <T : Option<*>> T.unaryPlus() = also(::register)
|
|
|
|
fun execute(block: Context.() -> Unit) {
|
|
validateExecute()
|
|
_execute = block
|
|
}
|
|
|
|
fun renderer(renderer: HelpRenderer) {
|
|
validateRenderer()
|
|
_renderer = renderer
|
|
}
|
|
|
|
internal abstract fun create(): COMMAND
|
|
|
|
internal fun build(): Command {
|
|
if (_execute == null) _execute = {}
|
|
if (_renderer == null) _renderer = DefaultHelpRenderer
|
|
|
|
validate()
|
|
return create()
|
|
}
|
|
|
|
private fun validateCommand(command: Command) {
|
|
if (commands.isNotEmpty()) {
|
|
val last = commands.last()
|
|
if (last is Fallback<*>) throw InvalidCommandOrder()
|
|
}
|
|
if (commands.any { it.name == command.name }) throw DuplicateCommand(command)
|
|
}
|
|
|
|
private fun validateArgument(argument: Argument<*>) {
|
|
if (arguments.isNotEmpty()) {
|
|
val last = arguments.last()
|
|
when {
|
|
last.unbounded -> throw InvalidArgumentOrder()
|
|
argument.required && last.optional -> throw InvalidArgumentOrder()
|
|
}
|
|
}
|
|
if (arguments.any { it.name == argument.name }) throw DuplicateArgument(argument)
|
|
}
|
|
|
|
private fun validateOption(option: Option<*>) {
|
|
for (name in option.names) {
|
|
if (options.any { name in it.names }) throw DuplicateOption(option)
|
|
}
|
|
}
|
|
|
|
private fun validateExecute() {
|
|
if (_execute != null) throw DuplicateExecute()
|
|
}
|
|
|
|
private fun validateRenderer() {
|
|
if (_renderer != null) throw DuplicateRenderer()
|
|
}
|
|
|
|
private fun validate() {
|
|
if (commands.any { it is Fallback<*> } && arguments.isNotEmpty()) throw InvalidFallback()
|
|
}
|
|
}
|
|
}
|