package com.infendro.cli.command import com.infendro.cli.command.Execution import com.infendro.cli.argument.Argument import com.infendro.cli.exception.build.* import com.infendro.cli.exception.run.ArgumentCountException import com.infendro.cli.exception.run.InvalidArgumentException import com.infendro.cli.exception.run.UnknownArgumentException import com.infendro.cli.exception.run.UnknownCommandException import com.infendro.cli.util.Regex.ARGUMENT import com.infendro.cli.util.Regex.ARGUMENT_NAME import com.infendro.cli.util.Regex.COMMAND_NAME class Command private constructor( private val index: Int, private val name: String?, private val commands: List, private val arguments: List>, private val block: Execution.() -> Unit, ) { private val nextIndex: Int get() = index + 1 fun run( args: Array, ) { val index = args .withIndex() .find { (_, value) -> value.startsWith("--") } ?.index ?: args.size val cmd = args.slice(0.. println("""argument "${e.argument}" must appear ${e.min} times""") e.max == Int.MAX_VALUE -> println("""argument "${e.argument}" must appear at least ${e.min} times""") else -> println("""argument "${e.argument}" must appear between ${e.min} and ${e.max} times""") } } } private fun run( cmd: List, arg: List, ) { if (index == cmd.lastIndex) { Execution( parseCmd(cmd), parseArg(arg), ).block() } else { val command = commands.firstOrNull() { it.name == cmd[nextIndex] || it.name == null } ?: throw UnknownCommandException(cmd[nextIndex]) command.run(cmd, arg) } } private fun parseCmd( cmd: List, ): List { return cmd.map { Execution.Cmd(it) } } private fun parseArg( arg: List, ): List { val result = mutableListOf() for (argument in arg) { val match = ARGUMENT.matchEntire(argument) ?: throw InvalidArgumentException(argument) val name = match.groups["name"]!!.value val value = match.groups["value"]?.value if (arguments.none { it.name == name }) throw UnknownArgumentException(name) result += Execution.Arg(name, value) } arguments .forEach { argument -> val count = result.count { it.name == argument.name } if (count !in argument.min..argument.max) throw ArgumentCountException(argument.name, argument.min, argument.max) } return result } class Builder internal constructor( private val index: Int, private val name: String?, ) { private val commands = mutableListOf() private val arguments = mutableListOf>() private lateinit var block: Execution.() -> Unit private val nextIndex: Int get() = index + 1 fun command( name: String? = null, block: Builder.(Key) -> Unit, ) { if (name != null && !COMMAND_NAME.matches(name)) throw IllegalCommandNameException(name) if (commands.any { it.name == name }) throw DuplicateCommandException(name) val builder = Builder(nextIndex, name) builder.block(Key(nextIndex)) val command = builder.build() commands.add(command) } fun argument( argument: Argument<*>, ) { if (!ARGUMENT_NAME.matches(argument.name)) throw IllegalArgumentNameException(argument.name) if (arguments.any { it.name == argument.name }) throw DuplicateArgumentException(argument.name) arguments.add(argument) } fun arguments( vararg arguments: Argument<*>, ) { for (argument in arguments) { argument(argument) } } fun execute( block: Execution.() -> Unit, ) { if (this::block.isInitialized) throw DuplicateExecuteException() this.block = block } fun build(): Command { if (!this::block.isInitialized) throw NoExecuteException() return Command(index, name, commands, arguments, block) } } class Key( internal val index: Int, ) } fun cli( block: Command.Builder.() -> Unit, ): Command { val builder = Command.Builder(-1, null) builder.block() return builder.build() }