-
Notifications
You must be signed in to change notification settings - Fork 121
/
MutuallyExclusiveOption.kt
152 lines (136 loc) · 5.52 KB
/
MutuallyExclusiveOption.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.github.ajalt.clikt.parameters.groups
import com.github.ajalt.clikt.core.*
import com.github.ajalt.clikt.internal.finalizeOptions
import com.github.ajalt.clikt.parameters.internal.NullableLateinit
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parsers.OptionInvocation
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class MutuallyExclusiveOptionTransformContext(val context: Context)
typealias MutuallyExclusiveOptionsTransform<OptT, OutT> = MutuallyExclusiveOptionTransformContext.(List<OptT>) -> OutT
class MutuallyExclusiveOptions<OptT : Any, OutT> internal constructor(
internal val options: List<OptionDelegate<out OptT?>>,
override val groupName: String?,
override val groupHelp: String?,
internal val transformAll: MutuallyExclusiveOptionsTransform<OptT, OutT>,
) : ParameterGroupDelegate<OutT> {
init {
require(options.size > 1) { "must provide at least two options to a mutually exclusive group" }
require(options.none { it.eager }) { "eager options are not allowed in mutually exclusive groups" }
}
private var value: OutT by NullableLateinit("Cannot read from group delegate before parsing command line")
override operator fun provideDelegate(
thisRef: BaseCliktCommand<*>,
property: KProperty<*>,
): ReadOnlyProperty<BaseCliktCommand<*>, OutT> {
thisRef.registerOptionGroup(this)
for (option in options) {
require(option.names.isNotEmpty()) { "must specify names for all options in a group" }
option.parameterGroup = this
option.groupName = groupName
thisRef.registerOption(option)
}
return this
}
override fun getValue(thisRef: BaseCliktCommand<*>, property: KProperty<*>): OutT = value
override fun finalize(
context: Context,
invocationsByOption: Map<Option, List<OptionInvocation>>,
) {
finalizeOptions(context, options, invocationsByOption)
val values = options.filter {
it in invocationsByOption || it.hasEnvvarOrSourcedValue(context, emptyList())
}.mapNotNull { it.value }
value = MutuallyExclusiveOptionTransformContext(context).transformAll(values)
}
override fun postValidate(context: Context) {
for (option in options) {
option.postValidate(context)
}
}
fun <T> copy(transformAll: MutuallyExclusiveOptionsTransform<OptT, T>) =
MutuallyExclusiveOptions(options, groupName, groupHelp, transformAll)
}
/**
* Set the name and help for this option.
*
* Although you would normally pass the name and help strings as arguments to
* [mutuallyExclusiveOptions], this function can be more convenient for long help strings.
*
* @param name The name of the group.
* @param help A help message to display for this group.
*/
fun <OptT : Any, OutT> MutuallyExclusiveOptions<OptT, OutT>.help(
name: String,
help: String,
): MutuallyExclusiveOptions<OptT, OutT> {
return MutuallyExclusiveOptions(options, name, help, transformAll)
}
/**
* Declare a set of two or more mutually exclusive options.
*
* If none of the options are given on the command line, the value of this delegate will be null.
* If one option is given, the value will be that option's value.
* If more than one option is given, the value of the last one is used.
*
* All options in the group must have a name specified. All options must be nullable (they cannot
* use [flag], [required] etc.). If you want flags, you should use [switch] instead.
*
* ### Example:
*
* ```
* val fruits: Int? by mutuallyExclusiveOptions(
* option("--apples").int(),
* option("--oranges").int()
* )
* ```
*
* @param name If given, the options in this group will be grouped together under this value in the
* help output
* @param help If given, this text will be added in help output to the group. If [name] is null,
* this value is not used.
*
* @see com.github.ajalt.clikt.parameters.options.switch
* @see com.github.ajalt.clikt.parameters.types.choice
*/
@Suppress("UnusedReceiverParameter")
fun <T : Any> ParameterHolder.mutuallyExclusiveOptions(
option1: OptionDelegate<out T?>,
option2: OptionDelegate<out T?>,
vararg options: OptionDelegate<out T?>,
name: String? = null,
help: String? = null,
): MutuallyExclusiveOptions<T, T?> {
return MutuallyExclusiveOptions(
listOf(option1, option2) + options,
name,
help
) { it.lastOrNull() }
}
/**
* If more than one of the group's options are given on the command line, throw a [MutuallyExclusiveGroupException]
*/
fun <T : Any> MutuallyExclusiveOptions<T, T?>.single(): MutuallyExclusiveOptions<T, T?> = copy {
if (it.size > 1) {
throw MutuallyExclusiveGroupException(options.map { o -> o.longestName()!! })
}
it.lastOrNull()
}
/**
* Make a [mutuallyExclusiveOptions] group required. If none of the options in the group are given,
* a [UsageError] is thrown.
*/
fun <T : Any> MutuallyExclusiveOptions<T, T?>.required(): MutuallyExclusiveOptions<T, T> {
return copy { values ->
transformAll(values) ?: run {
val names = options.joinToString { it.longestName()!! }
throw UsageError(context.localization.requiredMutexOption(names))
}
}
}
/**
* If none of the options in a [mutuallyExclusiveOptions] group are given on the command line, us [value] for the group.
*/
fun <T : Any> MutuallyExclusiveOptions<T, T?>.default(value: T): MutuallyExclusiveOptions<T, T> {
return copy { transformAll(it) ?: value }
}