package uk.co.nickthecoder.feather
// Copy/pasted with some modifications from Glok.
interface Arguments {
/**
* Is the single-character flag parameter present?
*/
fun flag(shortName: Char): Boolean
/**
* Is the multi-character flag parameter present?
*/
fun flag(longName: String): Boolean
/**
* Is either the single-character or the equivalent multi-character flag present.
*
* A convenience function equivalent to :
*
* flag(shortName) || flag(longName)
*
*/
fun flag(shortName: Char, longName: String) = flag(shortName) || flag(longName)
/**
* The value of a named parameter. If multiple values are present, then only the first is returned.
*/
fun value(name: String): String?
/**
* A Kotlin operator equivalent to [value]. i.e.
*
* myArgs["paramName"] == myArgs.value( "paramName" )
*/
operator fun get(name: String) = value(name)
/**
* The values of a named parameter, which is expected to have multiple values.
* If no values are found, this returns an empty list.
*/
fun values(name: String): List<String>
/**
* Remaining (unnamed) parameters.
*/
fun remainder(): List<String>
/**
* Used for debugging - lists all parameters.
*/
fun dump()
}
private class ArgumentsImpl : Arguments {
val flagsFound = mutableSetOf<Char>()
val longFlagsFound = mutableSetOf<String>()
val values = mutableMapOf<String, MutableList<String>>()
val remaining = mutableListOf<String>()
fun addValue(name: String, value: String) {
val existingList = values[name]
if (existingList == null) {
values[name] = mutableListOf(value)
} else {
existingList.add(value)
}
}
override fun flag(shortName: Char) = flagsFound.contains(shortName)
override fun flag(longName: String) = longFlagsFound.contains(longName)
override fun remainder() = remaining
override fun value(name: String) = values[name]?.first()
override fun values(name: String) = values[name] ?: emptyList()
override fun dump() {
println("flags : $flagsFound")
println("longFlags : $longFlagsFound")
println("values :")
for ((name, list) in values) {
println(" parameter $name : $list")
}
println("Remainder : $remaining")
}
}
/**
* Uses two dashes for long names. Single dashes are _only_ used for flags.
*
* Values are always separated by a space after the long name. e.g. --out result.txt
*
* This is my favourite convention, because it is less messy than the Posix convention,
* and doesn't have the problem with filename completion caused by the GNU convention.
*
* @param flags A list of single-character flags
* @param longFlags A list of multi-character flags
* @param parameters A list of parameter names
* @param args The arguments to be parsed (the values passed to the program's `main` method)
*
*/
fun doubleDashArguments(
flags: List<Char>,
longFlags: List<String>,
parameters: List<String>,
args: Array<out String>
): Arguments {
val result = ArgumentsImpl()
var endOfArguments = false
var skip = false
for ((index, arg) in args.withIndex()) {
if (skip) {
skip = false
continue
}
if (endOfArguments) {
result.remaining.add(arg)
} else {
if (arg.startsWith("--")) {
if (arg == "--") {
endOfArguments = true
} else {
val withoutDashes = arg.substring(2)
if (longFlags.contains(withoutDashes)) {
// e.g. --verbose
result.longFlagsFound.add(withoutDashes)
} else if (parameters.contains(withoutDashes)) {
// e.g. --out result.txt
if (args.size > index + 1) {
val value = args[index + 1]
result.addValue(withoutDashes, value)
skip = true
} else {
throw IllegalArgumentException("Expected a value after $arg")
}
} else {
throw IllegalArgumentException("Unexpected argument $arg")
}
}
} else if (arg.startsWith('-')) {
if (arg == "-") {
// Looks like flags, but none specified.
throw IllegalArgumentException("Unexpected argument $arg")
} else {
val withoutDash = arg.substring(1)
// A set of single character flags. e.g. -abc, where a, b and c are flags.
for (c in withoutDash) {
if (flags.contains(c)) {
result.flagsFound.add(c)
} else {
throw IllegalArgumentException("Unexpected argument $arg")
}
}
}
} else {
// Not an option e.g. result.txt
result.remaining.add(arg)
endOfArguments = true
}
}
}
return result
}