Exit Full View

Feather2 / src / main / kotlin / uk / co / nickthecoder / feather / Arguments.kt

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
}