Mercurial > cgi-bin > hgweb.cgi > PassMan
view src/main/kotlin/name/blackcap/passman/Arguments.kt @ 28:287eadf5ab30 default tip
Check for timeouts inside subcommands while in interactive mode as well.
author | David Barts <n5jrn@me.com> |
---|---|
date | Wed, 31 Jul 2024 11:21:18 -0700 |
parents | 3a3067ba673b |
children |
line wrap: on
line source
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 @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) { throw SubcommandException(message = e.message ?: "syntax error", status = 2) } if (commandLine.hasOption("help")) { HelpFormatter().printHelp("$SHORTNAME $name [options] csv_file", options) throw SubcommandException(status = 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 -> throw SubcommandException(message = "--$name value must not be empty") 1 -> return optionValue[0] else -> throw SubcommandException(message = "--$name value must be a single character") } } } fun parseInto(name: String, args: Array<String>, into: Any): Array<String> = AnnotationArgumentParser(name, args, into).parse()