131 lines
4.3 KiB
Kotlin
131 lines
4.3 KiB
Kotlin
package com.infendro.cli.command.context
|
|
|
|
import com.infendro.cli.command.Argument
|
|
import com.infendro.cli.command.Command
|
|
import com.infendro.cli.command.Option
|
|
import com.infendro.cli.command.context.Context.*
|
|
import com.infendro.cli.exception.run.*
|
|
|
|
internal object Parser {
|
|
fun parse(root: Command, args: Array<String>): Context {
|
|
var command = root
|
|
val cmd = mutableListOf<Cmd>()
|
|
val arg = mutableListOf<Arg>()
|
|
val opt = mutableListOf<Opt>()
|
|
|
|
var i = 0
|
|
|
|
// commands
|
|
while (i < args.size) {
|
|
val current = args[i]
|
|
|
|
if (current.startsWith("-"))
|
|
break
|
|
|
|
val c = command.commands.firstOrNull { it.name == current || it.fallback }
|
|
if (c == null) {
|
|
when {
|
|
command.arguments.isEmpty() -> throw UnknownCommandException(current)
|
|
else -> break
|
|
}
|
|
}
|
|
|
|
command = c
|
|
cmd += Cmd(current)
|
|
|
|
i++
|
|
}
|
|
|
|
// arguments and options
|
|
var endOfOptions = false
|
|
var argumentIndex = 0
|
|
while (i < args.size) {
|
|
val dashes = args[i].takeWhile { it == '-' }.count()
|
|
val current = args[i].drop(dashes)
|
|
|
|
when {
|
|
// argument
|
|
endOfOptions || dashes == 0 -> {
|
|
val argument = command.arguments.getOrNull(argumentIndex)
|
|
?: throw TooManyArgumentsException()
|
|
|
|
if (argument !is Argument.Variable)
|
|
argumentIndex++
|
|
|
|
val value = argument.parser.parse(current) as Any
|
|
arg += Arg(value)
|
|
}
|
|
|
|
// option
|
|
dashes in 1..2 -> {
|
|
if (dashes == 2 && current.isEmpty()) {
|
|
endOfOptions = true
|
|
i++
|
|
continue
|
|
}
|
|
|
|
val (name, text) = when {
|
|
current.contains('=') -> {
|
|
val (name, value) = current.split('=', limit = 2)
|
|
name to value
|
|
}
|
|
|
|
else -> {
|
|
val next = args.getOrNull(i + 1)
|
|
if (next != null && !next.startsWith('-')) {
|
|
i++
|
|
current to next
|
|
} else {
|
|
current to null
|
|
}
|
|
}
|
|
}
|
|
|
|
val names = when {
|
|
dashes == 1 -> name.map { "$it" }
|
|
else -> listOf(name)
|
|
}
|
|
|
|
for (name in names) {
|
|
val option = command.options.firstOrNull { name in it.names }
|
|
?: throw UnknownOptionException(name)
|
|
|
|
val value = when {
|
|
text != null -> option.parser.parse(text)
|
|
option.flag -> option.fallback!!
|
|
else -> throw MissingOptionValueException(name)
|
|
}
|
|
opt += Opt(name, value)
|
|
}
|
|
}
|
|
|
|
// malformed option
|
|
else -> {
|
|
when {
|
|
args[i].contains('=') -> {
|
|
val (option) = args[i].split('=', limit = 2)
|
|
throw MalformedOptionException(option)
|
|
}
|
|
else -> throw MalformedOptionException(args[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
i++
|
|
}
|
|
|
|
if (arg.size < command.arguments.count { it is Argument.Required })
|
|
throw MissingArgumentException()
|
|
|
|
for (option in command.options) {
|
|
val count = opt.count { it.name in option.names }
|
|
when {
|
|
option is Option.Required && count == 0 -> throw MissingOptionException(option.name)
|
|
option !is Option.Variable && count > 1 -> throw DuplicateOptionException(option.name)
|
|
}
|
|
}
|
|
|
|
return Context(command, cmd, opt, arg)
|
|
}
|
|
}
|