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, val arguments: List>, val options: List>, 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, arguments: List>, options: List>, execute: Context.() -> Unit, renderer: HelpRenderer, ) : Command(name, description, commands, arguments, options, execute, renderer) { class Builder( name: String, description: String?, ) : Command.Builder(name, description) { override fun create() = Named(name, description, commands, arguments, options, execute, renderer) } } class Fallback( val key: Key, val parser: Parser, name: String, description: String?, commands: List, arguments: List>, options: List>, execute: Context.() -> Unit, renderer: HelpRenderer, ) : Command(name, description, commands, arguments, options, execute, renderer) { class Builder( val parser: Parser, name: String, description: String?, ) : Command.Builder>(name, description) { internal val key = Key() override fun create() = Fallback(key, parser, name, description, commands, arguments, options, execute, renderer) } class Key } @Dsl sealed class Builder( protected val name: String, protected val description: String?, ) { protected val commands = mutableListOf() protected val arguments = mutableListOf>() protected val options = mutableListOf>() 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.unaryPlus() = also(::register) fun register(argument: Argument<*>) { validateArgument(argument) arguments += argument } fun register(vararg arguments: Argument<*>) = arguments.forEach(::register) operator fun > T.unaryPlus() = also(::register) fun register(option: Option<*>) { validateOption(option) options += option } fun register(vararg options: Option<*>) = options.forEach(::register) operator fun > 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() } } }