-
Notifications
You must be signed in to change notification settings - Fork 121
/
ChoiceGroup.kt
166 lines (152 loc) · 5.6 KB
/
ChoiceGroup.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package com.github.ajalt.clikt.parameters.groups
import com.github.ajalt.clikt.core.BadParameterValue
import com.github.ajalt.clikt.core.BaseCliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.MissingOption
import com.github.ajalt.clikt.parameters.internal.NullableLateinit
import com.github.ajalt.clikt.parameters.options.Option
import com.github.ajalt.clikt.parameters.options.OptionDelegate
import com.github.ajalt.clikt.parameters.options.RawOption
import com.github.ajalt.clikt.parameters.options.switch
import com.github.ajalt.clikt.parameters.types.choice
import com.github.ajalt.clikt.parsers.OptionInvocation
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class ChoiceGroup<GroupT : OptionGroup, OutT> internal constructor(
internal val option: OptionDelegate<String?>,
internal val groups: Map<String, GroupT>,
internal val transform: (GroupT?) -> OutT,
) : ParameterGroupDelegate<OutT> {
override val groupName: String? = null
override val groupHelp: String? = null
private var value: OutT by NullableLateinit("Cannot read from option delegate before parsing command line")
private var chosenGroup: OptionGroup? = null
init {
require(groups.none { it.value.options.any { o -> o.eager } }) {
"eager options are not allowed in choice and switch option groups"
}
}
override fun provideDelegate(
thisRef: BaseCliktCommand<*>,
property: KProperty<*>,
): ReadOnlyProperty<BaseCliktCommand<*>, OutT> {
option.provideDelegate(thisRef, property) // infer the option name and register it
thisRef.registerOptionGroup(this)
for ((_, group) in groups) {
for (option in group.options) {
option.parameterGroup = this
option.groupName = group.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>>,
) {
val key = option.value
if (key == null) {
value = transform(null)
// Finalize the group so that default groups have their options finalized
(value as? OptionGroup)?.let { g ->
g.finalize(context, invocationsByOption.filterKeys { it in g.options })
chosenGroup = g
}
return
}
val group = groups[key] ?: throw BadParameterValue(
context.localization.invalidGroupChoice(key, groups.keys.toList()),
option,
)
group.finalize(context, invocationsByOption.filterKeys { it in group.options })
chosenGroup = group
value = transform(group)
}
override fun postValidate(context: Context) {
chosenGroup?.options?.forEach { it.postValidate(context) }
}
}
/**
* Convert the option to an option group based on a fixed set of values.
*
* ### Example:
*
* ```
* option().groupChoice(mapOf("foo" to FooOptionGroup(), "bar" to BarOptionGroup()))
* ```
*
* @see com.github.ajalt.clikt.parameters.types.choice
*/
fun <T : OptionGroup> RawOption.groupChoice(choices: Map<String, T>): ChoiceGroup<T, T?> {
return ChoiceGroup(choice(choices.mapValues { it.key }), choices) { it }
}
/**
* Convert the option to an option group based on a fixed set of values.
*
* ### Example:
*
* ```
* option().groupChoice("foo" to FooOptionGroup(), "bar" to BarOptionGroup())
* ```
*
* @see com.github.ajalt.clikt.parameters.types.choice
*/
fun <T : OptionGroup> RawOption.groupChoice(vararg choices: Pair<String, T>): ChoiceGroup<T, T?> {
return groupChoice(choices.toMap())
}
/**
* If a [groupChoice] or [groupSwitch] option is not called on the command line, throw a
* [MissingOption] exception.
*
* ### Example:
*
* ```
* option().groupChoice("foo" to FooOptionGroup(), "bar" to BarOptionGroup()).required()
* ```
*/
fun <T : OptionGroup> ChoiceGroup<T, T?>.required(): ChoiceGroup<T, T> {
return ChoiceGroup(option, groups) { it ?: throw MissingOption(option) }
}
/**
* Convert the option into a set of flags that each map to an option group.
*
* ### Example:
*
* ```
* option().groupSwitch(mapOf("--foo" to FooOptionGroup(), "--bar" to BarOptionGroup()))
* ```
*/
fun <T : OptionGroup> RawOption.groupSwitch(choices: Map<String, T>): ChoiceGroup<T, T?> {
return ChoiceGroup(switch(choices.mapValues { it.key }), choices) { it }
}
/**
* Convert the option into a set of flags that each map to an option group.
*
* ### Example:
*
* ```
* option().groupSwitch("--foo" to FooOptionGroup(), "--bar" to BarOptionGroup())
* ```
*/
fun <T : OptionGroup> RawOption.groupSwitch(vararg choices: Pair<String, T>): ChoiceGroup<T, T?> {
return groupSwitch(choices.toMap())
}
/**
* If a [groupChoice] or [groupSwitch] option is not called on the command line, use the value of
* the group with a switch or choice [name].
*
* ### Example:
*
* ```
* option().groupChoice("foo" to FooOptionGroup(), "bar" to BarOptionGroup()).defaultByName("foo")
* option().groupSwitch("--foo" to FooOptionGroup(), "--bar" to BarOptionGroup()).defaultByName("--bar")
* ```
*
* @throws IllegalArgumentException if [name] is not one of the option's choice/switch names.
*/
fun <T : OptionGroup> ChoiceGroup<T, T?>.defaultByName(name: String): ChoiceGroup<T, T> {
require(name in groups) { "invalid default name $name (must be one of ${groups.keys})" }
return ChoiceGroup(option, groups) { it ?: groups.getValue(name) }
}