or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

arguments.mdconfiguration-sources.mdcore-commands.mdexceptions.mdindex.mdoptions.mdparameter-groups.mdparameter-types.mdshell-completion.mdtesting-utilities.md

parameter-groups.mddocs/

0

# Parameter Groups

1

2

Organize related parameters into groups with mutual exclusion, co-occurrence, and choice-based selection patterns.

3

4

## Capabilities

5

6

### Basic Option Groups

7

8

Group related options together for better help organization and validation.

9

10

```kotlin { .api }

11

/**

12

* Basic option group for organizing related parameters

13

* @param name Group name for help display

14

* @param help Help text for the group

15

*/

16

open class OptionGroup(val name: String? = null, val help: String? = null) {

17

/** Access to parent command */

18

protected val command: CliktCommand get() = TODO()

19

20

/** Create option in this group */

21

fun option(

22

vararg names: String,

23

help: String = "",

24

metavar: String? = null,

25

hidden: Boolean = false,

26

helpTags: Map<String, String> = emptyMap(),

27

envvar: String? = null,

28

valueSourceKey: String? = null,

29

completionCandidates: CompletionCandidates? = null

30

): RawOption

31

}

32

```

33

34

**Usage Examples:**

35

36

```kotlin

37

class DatabaseOptions : OptionGroup(name = "Database Options", help = "Database connection settings") {

38

val host by option("--db-host", help = "Database host").default("localhost")

39

val port by option("--db-port", help = "Database port").int().default(5432)

40

val username by option("--db-user", help = "Database username").required()

41

val password by option("--db-password", help = "Database password").required()

42

}

43

44

class LoggingOptions : OptionGroup(name = "Logging Options") {

45

val level by option("--log-level", help = "Logging level")

46

.choice("debug", "info", "warn", "error").default("info")

47

val file by option("--log-file", help = "Log file path")

48

}

49

50

class MyCommand : CliktCommand() {

51

private val database by DatabaseOptions()

52

private val logging by LoggingOptions()

53

54

override fun run() {

55

echo("Connecting to ${database.host}:${database.port}")

56

echo("Log level: ${logging.level}")

57

}

58

}

59

```

60

61

### Mutually Exclusive Options

62

63

Create groups where only one option can be specified at a time.

64

65

```kotlin { .api }

66

/**

67

* Create mutually exclusive option group

68

* @param options Options that are mutually exclusive

69

*/

70

fun ParameterHolder.mutuallyExclusiveOptions(

71

vararg options: OptionDelegate<*>

72

): MutuallyExclusiveOptions

73

74

/**

75

* Mutually exclusive option group

76

*/

77

class MutuallyExclusiveOptions : ParameterGroup {

78

val option: Option?

79

}

80

```

81

82

**Usage Examples:**

83

84

```kotlin

85

class MyCommand : CliktCommand() {

86

// Individual options

87

private val verbose by option("-v", "--verbose", help = "Verbose output").flag()

88

private val quiet by option("-q", "--quiet", help = "Quiet output").flag()

89

private val debug by option("-d", "--debug", help = "Debug output").flag()

90

91

// Make them mutually exclusive

92

private val outputMode by mutuallyExclusiveOptions(verbose, quiet, debug)

93

94

override fun run() {

95

when {

96

verbose -> echo("Verbose mode enabled")

97

quiet -> echo("Quiet mode enabled")

98

debug -> echo("Debug mode enabled")

99

else -> echo("Default output mode")

100

}

101

}

102

}

103

104

// Alternative approach with enum

105

class MyCommand2 : CliktCommand() {

106

enum class OutputMode { VERBOSE, QUIET, DEBUG }

107

108

private val verbose by option("-v", "--verbose", help = "Verbose output")

109

.flag().convert { OutputMode.VERBOSE }

110

private val quiet by option("-q", "--quiet", help = "Quiet output")

111

.flag().convert { OutputMode.QUIET }

112

private val debug by option("-d", "--debug", help = "Debug output")

113

.flag().convert { OutputMode.DEBUG }

114

115

private val outputMode by mutuallyExclusiveOptions(verbose, quiet, debug)

116

}

117

```

118

119

### Co-occurring Option Groups

120

121

Create groups where all options must be specified together.

122

123

```kotlin { .api }

124

/**

125

* Mark option group as co-occurring (all options required together)

126

*/

127

fun <T : OptionGroup> T.cooccurring(): ParameterGroupDelegate<T?>

128

```

129

130

**Usage Examples:**

131

132

```kotlin

133

class AuthOptions : OptionGroup(name = "Authentication") {

134

val username by option("--username", help = "Username").required()

135

val password by option("--password", help = "Password").required()

136

}

137

138

class TlsOptions : OptionGroup(name = "TLS Configuration") {

139

val certFile by option("--cert", help = "Certificate file").required()

140

val keyFile by option("--key", help = "Private key file").required()

141

val caFile by option("--ca", help = "CA certificate file")

142

}

143

144

class MyCommand : CliktCommand() {

145

// Either provide both username and password, or neither

146

private val auth by AuthOptions().cooccurring()

147

148

// Either provide cert and key (and optionally CA), or none

149

private val tls by TlsOptions().cooccurring()

150

151

override fun run() {

152

auth?.let { authOptions ->

153

echo("Authenticating as ${authOptions.username}")

154

} ?: echo("No authentication")

155

156

tls?.let { tlsOptions ->

157

echo("Using TLS with cert: ${tlsOptions.certFile}")

158

} ?: echo("No TLS")

159

}

160

}

161

```

162

163

### Choice Groups

164

165

Create groups where the user selects one of several predefined option sets.

166

167

```kotlin { .api }

168

/**

169

* Create choice group with predefined option sets

170

* @param choices Map of choice names to values

171

*/

172

fun <T : Any> CliktCommand.groupChoice(

173

vararg choices: Pair<String, T>

174

): ParameterGroupDelegate<T?>

175

```

176

177

**Usage Examples:**

178

179

```kotlin

180

// Define different deployment configurations

181

sealed class DeploymentConfig {

182

data class Development(val debugPort: Int = 8000) : DeploymentConfig()

183

data class Staging(val replicas: Int = 2) : DeploymentConfig()

184

data class Production(val replicas: Int = 5, val healthCheck: Boolean = true) : DeploymentConfig()

185

}

186

187

class MyCommand : CliktCommand() {

188

private val deployment by groupChoice(

189

"--dev" to DeploymentConfig.Development(),

190

"--staging" to DeploymentConfig.Staging(),

191

"--prod" to DeploymentConfig.Production()

192

)

193

194

override fun run() {

195

when (val config = deployment) {

196

is DeploymentConfig.Development -> {

197

echo("Development deployment with debug port ${config.debugPort}")

198

}

199

is DeploymentConfig.Staging -> {

200

echo("Staging deployment with ${config.replicas} replicas")

201

}

202

is DeploymentConfig.Production -> {

203

echo("Production deployment with ${config.replicas} replicas, health check: ${config.healthCheck}")

204

}

205

null -> {

206

echo("No deployment configuration specified")

207

}

208

}

209

}

210

}

211

212

// More complex choice groups with actual option groups

213

class DatabaseGroup : OptionGroup() {

214

val host by option("--db-host").default("localhost")

215

val port by option("--db-port").int().default(5432)

216

}

217

218

class FileGroup : OptionGroup() {

219

val path by option("--file-path").required()

220

val format by option("--file-format").choice("json", "xml").default("json")

221

}

222

223

class MyCommand2 : CliktCommand() {

224

private val source by groupChoice(

225

"--database" to DatabaseGroup(),

226

"--file" to FileGroup()

227

)

228

229

override fun run() {

230

when (val config = source) {

231

is DatabaseGroup -> {

232

echo("Using database at ${config.host}:${config.port}")

233

}

234

is FileGroup -> {

235

echo("Using file ${config.path} with format ${config.format}")

236

}

237

null -> {

238

echo("No data source specified")

239

}

240

}

241

}

242

}

243

```

244

245

### Advanced Group Patterns

246

247

Combine multiple group types for complex parameter relationships.

248

249

```kotlin { .api }

250

/**

251

* Parameter group interface

252

*/

253

interface ParameterGroup {

254

val groupName: String?

255

val groupHelp: String?

256

}

257

258

/**

259

* Parameter group delegate interface

260

*/

261

interface ParameterGroupDelegate<out T> {

262

operator fun getValue(thisRef: ParameterHolder, property: KProperty<*>): T

263

}

264

```

265

266

**Usage Examples:**

267

268

```kotlin

269

// Complex nested group structure

270

class ServerConfig : OptionGroup(name = "Server Configuration") {

271

val host by option("--host").default("0.0.0.0")

272

val port by option("--port").int().default(8080)

273

}

274

275

class DatabaseConfig : OptionGroup(name = "Database Configuration") {

276

val url by option("--db-url").required()

277

val poolSize by option("--db-pool-size").int().default(10)

278

}

279

280

class CacheConfig : OptionGroup(name = "Cache Configuration") {

281

val enabled by option("--cache-enabled").flag()

282

val ttl by option("--cache-ttl").int().default(3600)

283

}

284

285

class MyCommand : CliktCommand() {

286

// Required server config

287

private val server by ServerConfig()

288

289

// Either database or cache can be optional, but not both

290

private val database by DatabaseConfig().cooccurring()

291

private val cache by CacheConfig().cooccurring()

292

293

override fun run() {

294

require(database != null || cache != null) {

295

"Either database or cache configuration must be provided"

296

}

297

298

echo("Server: ${server.host}:${server.port}")

299

300

database?.let { db ->

301

echo("Database: ${db.url} (pool size: ${db.poolSize})")

302

}

303

304

cache?.let { c ->

305

if (c.enabled) {

306

echo("Cache enabled with TTL: ${c.ttl}s")

307

} else {

308

echo("Cache disabled")

309

}

310

}

311

}

312

}

313

314

// Validation across groups

315

class MyAdvancedCommand : CliktCommand() {

316

private val inputFile by option("--input").file()

317

private val outputFile by option("--output").file()

318

319

class ProcessingOptions : OptionGroup(name = "Processing") {

320

val threads by option("--threads").int().default(1)

321

val batchSize by option("--batch-size").int().default(100)

322

}

323

324

private val processing by ProcessingOptions()

325

326

override fun run() {

327

// Cross-group validation

328

if (inputFile == outputFile) {

329

echo("Warning: Input and output files are the same", err = true)

330

}

331

332

if (processing.threads > Runtime.getRuntime().availableProcessors()) {

333

echo("Warning: Thread count exceeds available processors", err = true)

334

}

335

336

echo("Processing with ${processing.threads} threads, batch size ${processing.batchSize}")

337

}

338

}

339

```

340

341

## Group Validation and Error Handling

342

343

```kotlin { .api }

344

/**

345

* Exception thrown when mutually exclusive options are used together

346

*/

347

class MutuallyExclusiveGroupException(val names: List<String>) : UsageError()

348

349

/**

350

* Custom group validation

351

*/

352

abstract class ParameterGroup {

353

/** Validate group after all parameters are parsed */

354

protected open fun validate() {}

355

}

356

```

357

358

**Usage Examples:**

359

360

```kotlin

361

class CustomValidationGroup : OptionGroup(name = "Custom Validation") {

362

val minValue by option("--min").int()

363

val maxValue by option("--max").int()

364

365

// Custom validation logic

366

override fun validate() {

367

val min = minValue

368

val max = maxValue

369

370

if (min != null && max != null && min > max) {

371

throw UsageError("Minimum value ($min) cannot be greater than maximum value ($max)")

372

}

373

}

374

}

375

376

class MyCommand : CliktCommand() {

377

private val range by CustomValidationGroup()

378

379

override fun run() {

380

echo("Range: ${range.minValue} to ${range.maxValue}")

381

}

382

}

383

```