Skip to content

Commit 1bc1474

Browse files
committed
Change Delegate into abstract class
This incompatible change now makes it easier to avoid incompatible changes going forward. This also moves all delegate registration to bind time (instead of construction time), and makes default into extension methods on Delegate and DelegateProvider. This removes the need to include explicit type parameter annotations in many cases when default generalizes the delegate's type.
1 parent 1421ca3 commit 1bc1474

File tree

3 files changed

+219
-75
lines changed

3 files changed

+219
-75
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99

1010
### Changed
1111

12+
- Made Delegate and DelegateProvider into abstract classes with internal
13+
constructors. This makes it much easier for me to separate internal and
14+
public parts of their API. THIS IS AN INCOMPATIBLE CHANGE, however it
15+
shouldn't really affect you unless you were trying to implement `Delegate`,
16+
which was't supported to begin with.
17+
18+
- `default` methods on both `Delegate` and `DelegateProvider` are now extension
19+
methods. This makes it possible to generalize the type when adding a
20+
default. This is most noticable when using a nullable value (or `null`
21+
itself) for the default, though may also be useful in other cases (eg: a
22+
"storing" that always produces a `Rectangle`, but you want the default to be
23+
a `Circle`. The resulting delegate will be a `Delegate<Shape>`.)
24+
25+
- Registration of delegates now takes place at binding-time rather than
26+
construction time. This should be pretty indistinguishable from the old
27+
behavior unless you're creating delegates without binding them.
28+
1229
- Started using keepachangelog.com format for CHANGELOG.md
1330

1431
- Made minor improvements to release process

src/main/kotlin/ArgParser.kt

Lines changed: 135 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ package com.xenomachina.argparser
2020

2121
import com.xenomachina.common.Holder
2222
import com.xenomachina.common.orElse
23+
import com.xenomachina.text.NBSP_CODEPOINT
2324
import com.xenomachina.text.term.codePointWidth
2425
import com.xenomachina.text.term.columnize
2526
import com.xenomachina.text.term.wrapText
2627
import java.io.Writer
28+
import java.util.LinkedHashSet
2729
import 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
*/
922982
class 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

Comments
 (0)