Mercurial > cgi-bin > hgweb.cgi > PassMan
diff src/main/kotlin/name/blackcap/passman/Arguments.kt @ 16:7a74ae668665
Add export subcommand.
author | David Barts <n5jrn@me.com> |
---|---|
date | Sun, 05 Feb 2023 10:50:39 -0800 |
parents | |
children | ea65ab890f66 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/name/blackcap/passman/Arguments.kt Sun Feb 05 10:50:39 2023 -0800 @@ -0,0 +1,104 @@ +package name.blackcap.passman + +import org.apache.commons.cli.* +import kotlin.reflect.KCallable +import kotlin.reflect.KMutableProperty +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.typeOf +import kotlin.system.exitProcess + +@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) +annotation class Argument( + val shortName: Char = AnnotationArgumentInfo.UNSPEC_SHORT, + val longName: String = AnnotationArgumentInfo.UNSPEC_LONG, + val description: String); + +private class AnnotationArgumentInfo(val property: KMutableProperty<*>) { + companion object { + const val UNSPEC_SHORT: Char = '\u0000' + const val UNSPEC_LONG: String = "" + } + val type = property.returnType + val annotation = property.findAnnotation<Argument>()!! + val longName = if (annotation.longName == UNSPEC_LONG) { property.name } else { annotation.longName } + val shortName = if (annotation.shortName == UNSPEC_SHORT) { longName.first() } else { annotation.shortName } +} + +private class AnnotationArgumentParser(val name: String, val args: Array<String>, val into: Any) { + private companion object { + val BOOLEAN_TYPE = typeOf<Boolean>() + val STRING_TYPE = typeOf<String>() + val CHAR_TYPE = typeOf<Char>() + } + + val annotated = into::class.members.filter { it is KMutableProperty<*> && it.hasAnnotation<Argument>() } + val options = Options() + lateinit var commandLine: CommandLine + + fun parse(): Array<String> { + build() + doParse() + return extract() + } + + private fun build() { + annotated.iterate { _, info -> + when { + info.type.isSubtypeOf(BOOLEAN_TYPE) -> + options.addOption(info.shortName.toString(), info.longName, false, info.annotation.description) + + info.type.isSubtypeOf(STRING_TYPE) || info.type.isSubtypeOf(CHAR_TYPE) -> + options.addOption(info.shortName.toString(), info.longName, true, info.annotation.description) + } + } + } + + private fun doParse() { + commandLine = try { + DefaultParser().parse(options, args) + } catch (e: ParseException) { + die(e.message ?: "syntax error", 2) + throw RuntimeException("this will never happen") + } + if (commandLine.hasOption("help")) { + HelpFormatter().printHelp("$SHORTNAME $name [options] csv_file", options) + exitProcess(0) + } + } + + private fun extract(): Array<String> { + annotated.iterate { annotated, info -> + if (commandLine.hasOption(info.longName)) { + when { + info.type.isSubtypeOf(BOOLEAN_TYPE) -> + annotated.setter.call(true) + info.type.isSubtypeOf(STRING_TYPE) -> + annotated.setter.call(commandLine.getOptionValue(info.longName)) + info.type.isSubtypeOf(CHAR_TYPE) -> + annotated.setter.call(commandLine.getCharOptionValue(info.longName)) + } + } + } + return commandLine.args + } + + private fun Collection<KCallable<*>>.iterate(block: (KMutableProperty<*>, AnnotationArgumentInfo) -> Unit) = + forEach { + block(it as KMutableProperty<*>, AnnotationArgumentInfo(it)) + } + + private fun CommandLine.getCharOptionValue(name: String): Char { + val optionValue = getOptionValue(name) + when (optionValue.length) { + 0 -> die("--$name value must not be empty") + 1 -> return optionValue[0] + else -> die("--$name value must be a single character") + } + throw RuntimeException("this will never happen") + } +} + +fun parseInto(name: String, args: Array<String>, into: Any): Array<String> = + AnnotationArgumentParser(name, args, into).parse()