@@ -20,10 +20,12 @@ package com.xenomachina.argparser
2020
2121import com.xenomachina.common.Holder
2222import com.xenomachina.common.orElse
23+ import com.xenomachina.text.NBSP_CODEPOINT
2324import com.xenomachina.text.term.codePointWidth
2425import com.xenomachina.text.term.columnize
2526import com.xenomachina.text.term.wrapText
2627import java.io.Writer
28+ import java.util.LinkedHashSet
2729import kotlin.reflect.KProperty
2830
2931/* *
@@ -132,9 +134,9 @@ class ArgParser(args: Array<out String>,
132134 help = help,
133135 usageArgument = errorName,
134136 isRepeating = true ) {
135- // preValidate ensures that this is non-null
136- value !! .value .add(transform(next()))
137- value.value
137+ val result = value.orElse { initialValue }
138+ result .add(transform(next()))
139+ result
138140 }.default(initialValue)
139141 }
140142
@@ -230,9 +232,6 @@ class ArgParser(args: Array<out String>,
230232 usageArgument = usageArgument,
231233 isRepeating = isRepeating,
232234 handler = handler)
233- for (name in names) {
234- registerOption(name, delegate)
235- }
236235 return delegate
237236 }
238237
@@ -255,11 +254,9 @@ class ArgParser(args: Array<out String>,
255254 help : String ,
256255 transform : String .() -> T
257256 ): Delegate <T > {
258- return object : WrappingDelegate <List <T >, T > (positionalList(name, 1 .. 1 , help = help, transform = transform)) {
259- override fun wrap (u : List <T >): T = u[0 ]
260-
261- override fun unwrap (w : T ): List <T > = listOf (w)
262- }
257+ return WrappingDelegate (
258+ positionalList(name, 1 .. 1 , help = help, transform = transform)
259+ ) { it[0 ] }
263260 }
264261
265262 /* *
@@ -310,9 +307,7 @@ class ArgParser(args: Array<out String>,
310307 throw IllegalArgumentException (" sizeRange only allows $last arguments, must allow at least 1" )
311308 }
312309
313- return PositionalDelegate <T >(this , name, sizeRange, help = help, transform = transform).apply {
314- positionalDelegates.add(this )
315- }
310+ return PositionalDelegate <T >(this , name, sizeRange, help = help, transform = transform)
316311 }
317312
318313 /* *
@@ -325,116 +320,133 @@ class ArgParser(args: Array<out String>,
325320 transform : String .() -> T
326321 ) = DelegateProvider { ident -> positionalList(identifierToArgName(ident), sizeRange, help, transform) }
327322
328- internal abstract class WrappingDelegate <U , W >(private val inner : Delegate <U >) : Delegate<W> {
323+ internal class WrappingDelegate <U , W >(
324+ private val inner : Delegate <U >,
325+ private val wrap : (U ) -> W
326+ ) : Delegate<W>() {
329327
330- abstract fun wrap ( u : U ): W
331- abstract fun unwrap ( w : W ): U
328+ override val parser : ArgParser
329+ get() = inner.parser
332330
333331 override val value: W
334332 get() = wrap(inner.value)
335333
334+ override val hasValue: Boolean
335+ get() = inner.hasValue
336+
336337 override val errorName: String
337338 get() = inner.errorName
338339
339340 override val help: String
340341 get() = inner.help
341342
342- override fun default (value : W ): Delegate <W > =
343- apply { inner.default(unwrap(value)) }
343+ override fun validate () {
344+ inner.validate()
345+ }
346+
347+ override fun toHelpFormatterValue (): HelpFormatter .Value = inner.toHelpFormatterValue()
344348
345349 override fun addValidator (validator : Delegate <W >.() -> Unit ): Delegate <W > =
346350 apply { validator(this ) }
351+
352+ override fun registerLeaf () {
353+ inner.registerLeaf()
354+ }
347355 }
348356
349- interface Delegate <T > {
357+ abstract class Delegate <out T > internal constructor() {
350358 /* * The value associated with this delegate */
351- val value: T
359+ abstract val value: T
352360
353361 /* * The name used to refer to this delegate's value in error messages */
354- val errorName: String
362+ abstract val errorName: String
355363
356364 /* * The user-visible help text for this delegate */
357- val help: String
365+ abstract val help: String
366+
367+ /* * Add validation logic. Validator should throw a [SystemExitException] on failure. */
368+ abstract fun addValidator (validator : Delegate <T >.() -> Unit ): Delegate <T >
358369
359370 /* * Allows this object to act as a property delegate */
360371 operator fun getValue (thisRef : Any? , property : KProperty <* >): T = value
361372
362- // Fluent setters:
373+ /* *
374+ * Allows this object to act as a property delegate provider.
375+ *
376+ * It provides itself, and also registers itself with the [ArgParser] at that time.
377+ */
378+ operator fun provideDelegate (thisRef : Any? , prop : KProperty <* >): ArgParser .Delegate <T > {
379+ registerRoot()
380+ return this
381+ }
363382
364- /* * Set default value */
365- fun default (value : T ): Delegate <T >
383+ abstract internal val parser: ArgParser
366384
367- /* * Add validation logic. Validator should throw a [SystemExitException] on failure. */
368- fun addValidator (validator : Delegate <T >.() -> Unit ): Delegate <T >
385+ /* *
386+ * Indicates whether or not a value has been set for this delegate
387+ */
388+ internal abstract val hasValue: Boolean
389+
390+ internal fun checkHasValue () {
391+ if (! hasValue) throw MissingValueException (errorName)
392+ }
393+
394+ internal abstract fun validate ()
395+
396+ internal abstract fun toHelpFormatterValue (): HelpFormatter .Value
369397
370- @Deprecated(" Use addValidator instead" )
371- fun addValidtator (validator : Delegate <T >.() -> Unit ) = addValidator(validator)
398+ internal fun registerRoot () {
399+ parser.checkNotParsed()
400+ parser.delegates.add(this )
401+ registerLeaf()
402+ }
403+
404+ internal abstract fun registerLeaf ()
372405 }
373406
374407 /* *
375408 * Provides a [Delegate] when given a name. This makes it possible to infer
376409 * a name for the `Delegate` based on the name it is bound to, rather than
377410 * specifying a name explicitly.
378411 */
379- class DelegateProvider <T >(private val ctor : (ident: String ) -> Delegate <T >) {
412+ class DelegateProvider <out T >(
413+ private val defaultHolder : Holder <T >? = null ,
414+ internal val ctor : (ident: String ) -> Delegate <T >
415+ ) {
380416 operator fun provideDelegate (thisRef : Any? , prop : KProperty <* >): Delegate <T > {
381- return ctor(prop.name).apply {
382- defaultValue?.let {
383- default(it.value)
384- }
385- }
386- }
387-
388- fun default (t : T ): DelegateProvider <T > = apply {
389- defaultValue = Holder (t)
417+ val delegate = ctor(prop.name)
418+ return (if (defaultHolder == null )
419+ delegate
420+ else
421+ delegate.default(defaultHolder.value)).provideDelegate(thisRef, prop)
390422 }
391-
392- private var defaultValue : Holder <T >? = null
393423 }
394424
395425 internal abstract class ParsingDelegate <T >(
396- val parser : ArgParser ,
426+ override val parser : ArgParser ,
397427 override val errorName : String ,
398- override val help : String ) : Delegate<T> {
428+ override val help : String ) : Delegate<T>() {
399429
400430 protected var holder: Holder <T >? = null
401431
402- init {
403- parser.assertNotParsed()
404- parser.delegates.add(this )
405- }
406-
407- /* *
408- * Sets the value for this Delegate. Should be called prior to parsing.
409- */
410- override fun default (value : T ): Delegate <T > {
411- parser.assertNotParsed()
412- holder = Holder (value)
413- return this
414- }
415-
416432 override fun addValidator (validator : Delegate <T >.() -> Unit ): Delegate <T > = apply {
417433 validators.add(validator)
418434 }
419435
420436 override val value: T
421437 get() {
422438 parser.force()
423- // preValidate ensures that this is non-null
439+ // checkHasValue should have ensured that this is non-null
424440 return holder!! .value
425441 }
426442
427- fun preValidate () {
428- if (holder == null )
429- throw MissingValueException (errorName)
430- }
443+ override val hasValue: Boolean
444+ get() = holder != null
431445
432- fun validate () {
446+ override fun validate () {
433447 for (validator in validators) validator()
434448 }
435449
436- abstract fun toHelpFormatterValue (): HelpFormatter .Value
437-
438450 private val validators = mutableListOf<Delegate <T >.() -> Unit > ()
439451 }
440452
@@ -463,6 +475,12 @@ class ArgParser(args: Array<out String>,
463475 isPositional = false ,
464476 help = help)
465477 }
478+
479+ override fun registerLeaf () {
480+ for (name in optionNames) {
481+ parser.registerOption(name, this )
482+ }
483+ }
466484 }
467485
468486 private class PositionalDelegate <T >(
@@ -472,6 +490,10 @@ class ArgParser(args: Array<out String>,
472490 help : String ,
473491 val transform : String .() -> T ) : ParsingDelegate<List<T>>(parser, errorName, help) {
474492
493+ override fun registerLeaf () {
494+ parser.positionalDelegates.add(this )
495+ }
496+
475497 fun parseArguments (args : List <String >) {
476498 holder = Holder (args.map(transform))
477499 }
@@ -541,7 +563,7 @@ class ArgParser(args: Array<out String>,
541563 private val shortOptionDelegates = mutableMapOf<Char , OptionDelegate <* >>()
542564 private val longOptionDelegates = mutableMapOf<String , OptionDelegate <* >>()
543565 private val positionalDelegates = mutableListOf<PositionalDelegate <* >>()
544- private val delegates = mutableListOf< ParsingDelegate <* >>()
566+ internal val delegates = LinkedHashSet < Delegate <* >>()
545567
546568 private fun <T > registerOption (name : String , delegate : OptionDelegate <T >) {
547569 if (name.startsWith(" --" )) {
@@ -577,7 +599,7 @@ class ArgParser(args: Array<out String>,
577599 if (! inValidation) {
578600 inValidation = true
579601 try {
580- for (delegate in delegates) delegate.preValidate ()
602+ for (delegate in delegates) delegate.checkHasValue ()
581603 for (delegate in delegates) delegate.validate()
582604 } finally {
583605 inValidation = false
@@ -589,7 +611,7 @@ class ArgParser(args: Array<out String>,
589611
590612 private var parseStarted = false
591613
592- private fun assertNotParsed () {
614+ internal fun checkNotParsed () {
593615 if (parseStarted) throw IllegalStateException (" arguments have already been parsed" )
594616 }
595617
@@ -746,8 +768,49 @@ class ArgParser(args: Array<out String>,
746768 help = " show this help message and exit" ,
747769 usageArgument = null ,
748770 isRepeating = false ) {
749- throw ShowHelpException (helpFormatter, delegates)
750- }.default(Unit )
771+ throw ShowHelpException (helpFormatter, delegates.toList())
772+ }.default(Unit ).registerRoot()
773+ }
774+ }
775+ }
776+
777+ fun <T > ArgParser.DelegateProvider<T>.default (newDefault : T ): ArgParser .DelegateProvider <T > {
778+ return ArgParser .DelegateProvider (ctor = ctor, defaultHolder = Holder (newDefault))
779+ }
780+
781+ fun <T > ArgParser.Delegate<T>.default (defaultValue : T ): ArgParser .Delegate <T > {
782+ val inner = this
783+
784+ return object : ArgParser .Delegate <T >() {
785+ override fun toHelpFormatterValue (): HelpFormatter .Value = inner.toHelpFormatterValue().copy(isRequired = false )
786+
787+ override fun validate () {
788+ inner.validate()
789+ }
790+
791+ override val parser: ArgParser
792+ get() = inner.parser
793+
794+ override val value: T
795+ get() {
796+ inner.parser.force()
797+ return if (inner.hasValue) inner.value else defaultValue
798+ }
799+
800+ override val hasValue: Boolean
801+ get() = true
802+
803+ override val errorName: String
804+ get() = inner.errorName
805+
806+ override val help: String
807+ get() = inner.help
808+
809+ override fun addValidator (validator : ArgParser .Delegate <T >.() -> Unit ): ArgParser .Delegate <T > =
810+ inner.addValidator() { validator(inner) }
811+
812+ override fun registerLeaf () {
813+ inner.registerLeaf()
751814 }
752815 }
753816}
@@ -783,9 +846,6 @@ interface HelpFormatter {
783846 val help : String )
784847}
785848
786- // TODO make verison in com.xenomachina.text public
787- internal const val NBSP_CODEPOINT = 160
788-
789849/* *
790850 * Default implementation of [HelpFormatter]. Output is modelled after that of common UNIX utilities and looks
791851 * something like this:
@@ -921,7 +981,7 @@ class DefaultHelpFormatter(
921981 */
922982class ShowHelpException internal constructor(
923983 private val helpFormatter : HelpFormatter ,
924- private val delegates : List <ArgParser .ParsingDelegate <* >>
984+ private val delegates : List <ArgParser .Delegate <* >>
925985) : SystemExitException(" Help was requested" , 0 ) {
926986 override fun printUserMessage (writer : Writer , progName : String? , columns : Int ) {
927987 writer.write(helpFormatter.format(progName, columns, delegates.map { it.toHelpFormatterValue() }))
0 commit comments