package com.infendro.cli.command import com.infendro.cli.command.context.Context import com.infendro.cli.command.context.Parser import com.infendro.cli.exception.build.* import com.infendro.cli.exception.run.CliException import com.infendro.cli.util.Regex.COMMAND import com.infendro.cli.util.Regex.OPTION class Command private constructor( val name: String?, val commands: List, val arguments: List>, val options: List>, internal val execute: Context.() -> Unit, ) { val fallback: Boolean get() = name == null fun run(args: Array) = try { Parser.parse(this, args).execute() } catch (e: CliException) { println(e.message) } class Builder internal constructor( private val level: Int, private val name: String?, ) { private val commands = mutableListOf() private val arguments = mutableListOf>() private val options = mutableListOf>() private lateinit var _execute: Context.() -> Unit private val key: Key get() = Key(level) fun command(name: String? = null, block: Builder.(Key) -> Unit) { // validate val last = commands.lastOrNull() when { last != null && last.fallback -> throw CommandOrderException() name != null && !name.matches(COMMAND) -> throw IllegalCommandNameException(name) commands.any { it.name == name } -> throw DuplicateCommandException(name) } commands += Builder(level + 1, name).also { it.block(it.key) }.build() } fun argument(argument: Argument<*>) { // validate val last = arguments.lastOrNull() when { last is Argument.Variable -> throw ArgumentOrderException() argument.required && last != null && !last.required -> throw ArgumentOrderException() } arguments += argument } fun > T.register() = also(::argument) fun arguments(vararg arguments: Argument<*>) = arguments.forEach(::argument) fun > Array.register() = also(::arguments) fun option(option: Option<*>) { // validate for (name in option.names) { when { !name.matches(OPTION) -> throw IllegalOptionNameException(name) options.any { name in it.names } -> throw DuplicateOptionException(name) } } options += option } fun > T.register() = also(::option) fun options(vararg options: Option<*>) = options.forEach(::option) fun > Array.register() = also(::options) fun execute(block: Context.() -> Unit) { if (::_execute.isInitialized) throw DuplicateExecuteException() _execute = block } fun build(): Command { // validate val last = commands.lastOrNull() when { last != null && last.fallback && arguments.isNotEmpty() -> throw InvalidFallbackException() } if (!::_execute.isInitialized) _execute = {} return Command(name, commands, arguments, options, _execute) } } class Key internal constructor( internal val index: Int, ) } fun cli(block: Command.Builder.() -> Unit) = Command.Builder(-1, null).also(block).build()