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): Context { var command = root val cmd = mutableListOf() val arg = mutableListOf() val opt = mutableListOf() 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) } }