0
# Configuration Sources
1
2
Load parameter values from external sources including environment variables, configuration files, and custom value providers with priority chaining.
3
4
## Capabilities
5
6
### Value Source Interface
7
8
Base interface for loading parameter values from external sources.
9
10
```kotlin { .api }
11
/**
12
* Interface for loading parameter values from external sources
13
*/
14
interface ValueSource {
15
/**
16
* Represents a parameter invocation with its values
17
*/
18
data class Invocation(val values: List<String>)
19
20
/**
21
* Get parameter values from this source
22
* @param context Current parsing context
23
* @param option Option to get values for
24
* @return List of invocations with values
25
*/
26
fun getValues(context: Context, option: Option): List<Invocation>
27
}
28
```
29
30
### Map Value Source
31
32
Load parameter values from a map (useful for testing and simple configuration).
33
34
```kotlin { .api }
35
/**
36
* Value source that loads values from a map
37
* @param map Map of parameter names to values
38
* @param getKey Function to get map key from option names
39
*/
40
class MapValueSource(
41
private val map: Map<String, String>,
42
private val getKey: (Option) -> String = { option ->
43
option.valueSourceKey ?: option.names.maxByOrNull { it.length } ?: ""
44
}
45
) : ValueSource {
46
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation>
47
}
48
```
49
50
**Usage Examples:**
51
52
```kotlin
53
class MyCommand : CliktCommand() {
54
private val host by option("--host", help = "Server host", valueSourceKey = "host")
55
private val port by option("--port", help = "Server port", valueSourceKey = "port").int()
56
private val debug by option("--debug", help = "Debug mode", valueSourceKey = "debug").flag()
57
58
override fun run() {
59
echo("Host: $host")
60
echo("Port: $port")
61
echo("Debug: $debug")
62
}
63
}
64
65
// Using map value source
66
fun main() {
67
val config = mapOf(
68
"host" to "example.com",
69
"port" to "8080",
70
"debug" to "true"
71
)
72
73
MyCommand()
74
.context {
75
valueSource = MapValueSource(config)
76
}
77
.main()
78
}
79
80
// Custom key mapping
81
val customKeySource = MapValueSource(
82
map = mapOf(
83
"server_host" to "localhost",
84
"server_port" to "3000"
85
),
86
getKey = { option ->
87
when (option.valueSourceKey) {
88
"host" -> "server_host"
89
"port" -> "server_port"
90
else -> option.valueSourceKey ?: ""
91
}
92
}
93
)
94
```
95
96
### Chained Value Source
97
98
Chain multiple value sources with priority ordering.
99
100
```kotlin { .api }
101
/**
102
* Value source that chains multiple sources in priority order
103
* Sources are checked in order, first match wins
104
* @param sources List of value sources in priority order
105
*/
106
class ChainedValueSource(private val sources: List<ValueSource>) : ValueSource {
107
constructor(vararg sources: ValueSource) : this(sources.toList())
108
109
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation>
110
}
111
```
112
113
**Usage Examples:**
114
115
```kotlin
116
class MyCommand : CliktCommand() {
117
private val apiKey by option("--api-key", help = "API key",
118
valueSourceKey = "api_key", envvar = "API_KEY")
119
private val timeout by option("--timeout", help = "Timeout seconds",
120
valueSourceKey = "timeout").int().default(30)
121
122
override fun run() {
123
echo("API Key: ${apiKey?.take(8)}...")
124
echo("Timeout: $timeout")
125
}
126
}
127
128
fun main() {
129
// Priority: command line > environment > config file > defaults
130
val configFile = mapOf(
131
"api_key" to "default-key-from-config",
132
"timeout" to "60"
133
)
134
135
val environmentVars = mapOf(
136
"API_KEY" to "env-api-key",
137
"TIMEOUT" to "45"
138
)
139
140
val chainedSource = ChainedValueSource(
141
MapValueSource(environmentVars), // Higher priority
142
MapValueSource(configFile) // Lower priority
143
)
144
145
MyCommand()
146
.context {
147
valueSource = chainedSource
148
}
149
.main()
150
}
151
152
// Complex chaining example
153
class DatabaseCommand : CliktCommand() {
154
private val dbUrl by option("--db-url", valueSourceKey = "database.url", envvar = "DATABASE_URL")
155
private val dbUser by option("--db-user", valueSourceKey = "database.user", envvar = "DATABASE_USER")
156
private val dbPassword by option("--db-password", valueSourceKey = "database.password", envvar = "DATABASE_PASSWORD")
157
158
override fun run() {
159
echo("Connecting to database...")
160
echo("URL: $dbUrl")
161
echo("User: $dbUser")
162
}
163
}
164
165
fun createConfiguredCommand(): DatabaseCommand {
166
// Load from multiple sources
167
val prodConfig = mapOf(
168
"database.url" to "postgres://prod-server:5432/myapp",
169
"database.user" to "prod_user"
170
)
171
172
val devConfig = mapOf(
173
"database.url" to "postgres://localhost:5432/myapp_dev",
174
"database.user" to "dev_user",
175
"database.password" to "dev_password"
176
)
177
178
val secrets = mapOf(
179
"database.password" to "super-secret-password"
180
)
181
182
val valueSource = ChainedValueSource(
183
MapValueSource(secrets), // Highest priority (secrets)
184
MapValueSource(prodConfig), // Production overrides
185
MapValueSource(devConfig) // Development defaults
186
)
187
188
return DatabaseCommand().context {
189
this.valueSource = valueSource
190
}
191
}
192
```
193
194
### Properties Value Source (JVM Platform)
195
196
Load parameter values from Java properties files (available on JVM platform only).
197
198
```kotlin { .api }
199
/**
200
* Value source that loads values from Java properties files
201
* Available on JVM platform only
202
* @param properties Properties object
203
* @param getKey Function to get property key from option
204
*/
205
class PropertiesValueSource(
206
private val properties: Properties,
207
private val getKey: (Option) -> String = { option ->
208
option.valueSourceKey ?: option.names.maxByOrNull { it.length }?.removePrefix("--") ?: ""
209
}
210
) : ValueSource {
211
companion object {
212
/**
213
* Create from properties file
214
* @param file Properties file to load
215
*/
216
fun fromFile(file: File): PropertiesValueSource
217
218
/**
219
* Create from properties file path
220
* @param path Path to properties file
221
*/
222
fun fromFile(path: String): PropertiesValueSource
223
224
/**
225
* Create from input stream
226
* @param inputStream Stream containing properties data
227
*/
228
fun fromInputStream(inputStream: InputStream): PropertiesValueSource
229
}
230
231
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation>
232
}
233
```
234
235
**Usage Examples:**
236
237
```kotlin
238
// app.properties file:
239
// server.host=localhost
240
// server.port=8080
241
// database.url=jdbc:postgresql://localhost:5432/myapp
242
// logging.level=INFO
243
244
class MyJvmCommand : CliktCommand() {
245
private val host by option("--host", valueSourceKey = "server.host")
246
private val port by option("--port", valueSourceKey = "server.port").int()
247
private val dbUrl by option("--db-url", valueSourceKey = "database.url")
248
private val logLevel by option("--log-level", valueSourceKey = "logging.level")
249
250
override fun run() {
251
echo("Server: $host:$port")
252
echo("Database: $dbUrl")
253
echo("Log level: $logLevel")
254
}
255
}
256
257
fun main() {
258
val propsSource = PropertiesValueSource.fromFile("app.properties")
259
260
MyJvmCommand()
261
.context {
262
valueSource = propsSource
263
}
264
.main()
265
}
266
267
// Multiple properties files with chaining
268
fun createProductionCommand(): MyJvmCommand {
269
val defaultProps = PropertiesValueSource.fromFile("defaults.properties")
270
val envProps = PropertiesValueSource.fromFile("production.properties")
271
val localProps = PropertiesValueSource.fromFile("local.properties")
272
273
val chainedSource = ChainedValueSource(
274
localProps, // Highest priority (local overrides)
275
envProps, // Environment-specific config
276
defaultProps // Lowest priority (defaults)
277
)
278
279
return MyJvmCommand().context {
280
valueSource = chainedSource
281
}
282
}
283
284
// Properties with custom key mapping
285
class DatabaseCommand : CliktCommand() {
286
private val host by option("--db-host", help = "Database host")
287
private val port by option("--db-port", help = "Database port").int()
288
private val name by option("--db-name", help = "Database name")
289
290
override fun run() {
291
echo("Connecting to $name at $host:$port")
292
}
293
}
294
295
val dbPropsSource = PropertiesValueSource(
296
Properties().apply {
297
setProperty("db_host", "prod-db-server")
298
setProperty("db_port", "5432")
299
setProperty("db_name", "production")
300
},
301
getKey = { option ->
302
when (option.names.first()) {
303
"--db-host" -> "db_host"
304
"--db-port" -> "db_port"
305
"--db-name" -> "db_name"
306
else -> ""
307
}
308
}
309
)
310
```
311
312
### Custom Value Sources
313
314
Create custom value sources for specialized configuration needs.
315
316
```kotlin { .api }
317
/**
318
* Custom value source implementation
319
*/
320
abstract class CustomValueSource : ValueSource {
321
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation>
322
}
323
```
324
325
**Usage Examples:**
326
327
```kotlin
328
// JSON configuration file value source
329
class JsonValueSource(private val jsonFile: File) : ValueSource {
330
private val config: Map<String, Any> by lazy {
331
// Parse JSON file (using your preferred JSON library)
332
parseJsonFile(jsonFile)
333
}
334
335
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation> {
336
val key = option.valueSourceKey ?: return emptyList()
337
val value = getNestedValue(config, key) ?: return emptyList()
338
return listOf(ValueSource.Invocation(listOf(value.toString())))
339
}
340
341
private fun getNestedValue(map: Map<String, Any>, key: String): Any? {
342
val parts = key.split(".")
343
var current: Any? = map
344
345
for (part in parts) {
346
current = (current as? Map<*, *>)?.get(part)
347
if (current == null) break
348
}
349
350
return current
351
}
352
}
353
354
// YAML configuration value source
355
class YamlValueSource(private val yamlFile: File) : ValueSource {
356
private val config: Map<String, Any> by lazy {
357
parseYamlFile(yamlFile)
358
}
359
360
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation> {
361
val key = option.valueSourceKey ?: return emptyList()
362
val value = getNestedValue(config, key)
363
364
return when (value) {
365
is List<*> -> listOf(ValueSource.Invocation(value.map { it.toString() }))
366
null -> emptyList()
367
else -> listOf(ValueSource.Invocation(listOf(value.toString())))
368
}
369
}
370
}
371
372
// Database configuration value source
373
class DatabaseConfigSource(private val connectionString: String) : ValueSource {
374
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation> {
375
val key = option.valueSourceKey ?: return emptyList()
376
377
// Query database for configuration value
378
val value = queryDatabase(connectionString, key)
379
return if (value != null) {
380
listOf(ValueSource.Invocation(listOf(value)))
381
} else {
382
emptyList()
383
}
384
}
385
386
private fun queryDatabase(connection: String, key: String): String? {
387
// Implementation would connect to database and query config table
388
// Return null if key not found
389
return null
390
}
391
}
392
393
// HTTP API configuration source
394
class HttpConfigSource(private val apiUrl: String, private val apiKey: String) : ValueSource {
395
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation> {
396
val key = option.valueSourceKey ?: return emptyList()
397
398
try {
399
val value = fetchConfigValue(apiUrl, key, apiKey)
400
return if (value != null) {
401
listOf(ValueSource.Invocation(listOf(value)))
402
} else {
403
emptyList()
404
}
405
} catch (e: Exception) {
406
// Log error and return empty result
407
return emptyList()
408
}
409
}
410
411
private fun fetchConfigValue(url: String, key: String, token: String): String? {
412
// Implementation would make HTTP request to fetch config
413
return null
414
}
415
}
416
```
417
418
### Environment Variable Integration
419
420
Combine value sources with environment variable support.
421
422
```kotlin { .api }
423
/**
424
* Environment variable value source
425
*/
426
class EnvironmentValueSource : ValueSource {
427
override fun getValues(context: Context, option: Option): List<ValueSource.Invocation>
428
}
429
```
430
431
**Usage Examples:**
432
433
```kotlin
434
class MyCommand : CliktCommand() {
435
// Options with environment variable support
436
private val apiKey by option("--api-key",
437
help = "API key",
438
envvar = "API_KEY",
439
valueSourceKey = "api.key")
440
441
private val dbUrl by option("--database-url",
442
help = "Database URL",
443
envvar = "DATABASE_URL",
444
valueSourceKey = "database.url")
445
446
override fun run() {
447
echo("API Key: ${apiKey?.take(8)}...")
448
echo("Database URL: $dbUrl")
449
}
450
}
451
452
// Priority chain: CLI args > env vars > config file > defaults
453
fun createConfiguredApp(): MyCommand {
454
val configFileSource = JsonValueSource(File("config.json"))
455
val envSource = EnvironmentValueSource()
456
457
val chainedSource = ChainedValueSource(
458
envSource, // Environment variables (higher priority)
459
configFileSource // Config file (lower priority)
460
)
461
462
return MyCommand().context {
463
valueSource = chainedSource
464
}
465
}
466
467
// Complex configuration hierarchy
468
class WebServerCommand : CliktCommand() {
469
private val port by option("--port", valueSourceKey = "server.port", envvar = "PORT").int()
470
private val host by option("--host", valueSourceKey = "server.host", envvar = "HOST")
471
private val ssl by option("--ssl", valueSourceKey = "server.ssl.enabled", envvar = "SSL_ENABLED").flag()
472
private val certFile by option("--cert", valueSourceKey = "server.ssl.cert", envvar = "SSL_CERT")
473
474
override fun run() {
475
echo("Starting server on $host:$port")
476
if (ssl) {
477
echo("SSL enabled with cert: $certFile")
478
}
479
}
480
}
481
482
fun createWebServer(): WebServerCommand {
483
// Multiple configuration sources
484
val sources = ChainedValueSource(
485
EnvironmentValueSource(), // Environment variables
486
PropertiesValueSource.fromFile("production.properties"), // Production config
487
PropertiesValueSource.fromFile("application.properties"), // Default config
488
MapValueSource(mapOf( // Fallback defaults
489
"server.port" to "8080",
490
"server.host" to "0.0.0.0",
491
"server.ssl.enabled" to "false"
492
))
493
)
494
495
return WebServerCommand().context {
496
valueSource = sources
497
}
498
}
499
```
500
501
## Experimental API
502
503
```kotlin { .api }
504
/**
505
* Annotation marking experimental value source APIs
506
*/
507
@RequiresOptIn("Value source API is experimental and may change")
508
annotation class ExperimentalValueSourceApi
509
```