0
# Command-Line Interface
1
2
CLI building and option parsing utilities for creating robust command-line applications with support for various argument patterns and help text generation.
3
4
## Capabilities
5
6
### CliBuilder with Apache Commons CLI
7
8
Command-line interface builder using Apache Commons CLI library for robust option parsing.
9
10
```groovy { .api }
11
/**
12
* Command-line interface builder using Apache Commons CLI
13
*/
14
class CliBuilder {
15
/** Create CliBuilder with default settings */
16
CliBuilder()
17
18
/** Create CliBuilder with usage text */
19
CliBuilder(String usage)
20
21
/** Parse command-line arguments */
22
OptionAccessor parse(String[] args)
23
24
/** Parse arguments and handle errors */
25
OptionAccessor parse(String[] args, boolean stopAtNonOption)
26
27
/** Print usage information */
28
void usage()
29
30
/** Print usage to specific writer */
31
void usage(PrintWriter writer)
32
33
/** Print usage to specific writer with width */
34
void usage(PrintWriter writer, int width)
35
36
/** Set usage text */
37
void setUsage(String usage)
38
39
/** Set header text */
40
void setHeader(String header)
41
42
/** Set footer text */
43
void setFooter(String footer)
44
45
/** Set whether to stop at non-option */
46
void setStopAtNonOption(boolean stopAtNonOption)
47
48
/** Add option with short name */
49
void option(String shortOpt, String description)
50
51
/** Add option with long name */
52
void longOpt(String longOpt, String description)
53
54
/** Add option with argument */
55
void optionWithArg(String shortOpt, String longOpt, String description)
56
57
/** Add option with optional argument */
58
void optionWithOptionalArg(String shortOpt, String longOpt, String description)
59
}
60
61
/**
62
* Accessor for parsed command-line options
63
*/
64
class OptionAccessor {
65
/** Check if option was provided */
66
boolean hasOption(String option)
67
68
/** Get option value */
69
Object getOptionValue(String option)
70
71
/** Get option value with default */
72
Object getOptionValue(String option, Object defaultValue)
73
74
/** Get option values (for options that can appear multiple times) */
75
String[] getOptionValues(String option)
76
77
/** Get remaining arguments */
78
String[] getArgs()
79
80
/** Get arguments as list */
81
List<String> arguments()
82
}
83
```
84
85
**Usage Examples:**
86
87
```groovy
88
import groovy.cli.commons.*
89
90
// Basic CLI setup
91
def cli = new CliBuilder(usage: 'myapp [options] <files>')
92
cli.setHeader('My Application - File Processor')
93
cli.setFooter('For more information, visit: https://example.com')
94
95
// Define options
96
cli.h(longOpt: 'help', 'Show this help message')
97
cli.v(longOpt: 'verbose', 'Verbose output')
98
cli.q(longOpt: 'quiet', 'Quiet mode')
99
cli.o(longOpt: 'output', args: 1, argName: 'file', 'Output file')
100
cli.f(longOpt: 'format', args: 1, argName: 'format', 'Output format (xml|json|csv)')
101
cli.r(longOpt: 'recursive', 'Process directories recursively')
102
cli.n(longOpt: 'dry-run', 'Show what would be done without actually doing it')
103
104
// Parse arguments
105
def options = cli.parse(args)
106
107
if (!options) {
108
// Parsing failed
109
return
110
}
111
112
if (options.h) {
113
cli.usage()
114
return
115
}
116
117
// Access options
118
boolean verbose = options.v
119
boolean quiet = options.q
120
boolean recursive = options.r
121
boolean dryRun = options.n
122
123
String outputFile = options.o
124
String format = options.getOptionValue('f', 'json') // default to 'json'
125
126
// Get remaining arguments (files to process)
127
def files = options.arguments()
128
129
if (files.isEmpty()) {
130
println "Error: No input files specified"
131
cli.usage()
132
return
133
}
134
135
// Process based on options
136
if (verbose && !quiet) {
137
println "Processing ${files.size()} files"
138
println "Output format: $format"
139
if (outputFile) {
140
println "Output file: $outputFile"
141
}
142
if (recursive) {
143
println "Recursive processing enabled"
144
}
145
if (dryRun) {
146
println "DRY RUN MODE - no changes will be made"
147
}
148
}
149
150
files.each { file ->
151
if (verbose && !quiet) {
152
println "Processing: $file"
153
}
154
155
if (!dryRun) {
156
// Actually process the file
157
processFile(file, format, outputFile, recursive)
158
}
159
}
160
```
161
162
### Advanced Option Configuration
163
164
Configure complex option patterns with validation and type conversion.
165
166
```groovy
167
// Advanced CLI with multiple argument types
168
def cli = new CliBuilder(usage: 'deploy [options] <environment>')
169
170
// String options
171
cli.u(longOpt: 'user', args: 1, argName: 'username', 'Username for deployment')
172
cli.p(longOpt: 'password', args: 1, argName: 'password', 'Password (prompt if not provided)')
173
174
// Numeric options
175
cli.t(longOpt: 'timeout', args: 1, argName: 'seconds', type: Integer, 'Timeout in seconds (default: 300)')
176
cli.r(longOpt: 'retries', args: 1, argName: 'count', type: Integer, 'Number of retry attempts (default: 3)')
177
178
// Multiple values
179
cli.s(longOpt: 'service', args: Option.UNLIMITED_VALUES, argName: 'service', 'Services to deploy (can be specified multiple times)')
180
cli.e(longOpt: 'exclude', args: Option.UNLIMITED_VALUES, argName: 'pattern', 'Exclude patterns')
181
182
// Optional arguments
183
cli.c(longOpt: 'config', optionalArg: true, argName: 'file', 'Configuration file (default: config.yml)')
184
185
// Boolean flags
186
cli.f(longOpt: 'force', 'Force deployment even if validation fails')
187
cli.v(longOpt: 'verbose', 'Verbose output')
188
cli.q(longOpt: 'quiet', 'Quiet mode')
189
190
def options = cli.parse(args)
191
192
if (!options || options.arguments().isEmpty()) {
193
println "Error: Environment must be specified"
194
cli.usage()
195
return
196
}
197
198
// Extract values with defaults and validation
199
String environment = options.arguments()[0]
200
String user = options.u
201
String password = options.p
202
Integer timeout = options.t ? options.t as Integer : 300
203
Integer retries = options.r ? options.r as Integer : 3
204
String configFile = options.c ?: 'config.yml'
205
206
// Handle multiple values
207
List<String> services = options.ss ?: [] // Note: multiple values accessed with repeated short option
208
List<String> excludePatterns = options.es ?: []
209
210
// Validation
211
if (!user) {
212
println "Error: Username is required"
213
cli.usage()
214
return
215
}
216
217
if (timeout < 1 || timeout > 3600) {
218
println "Error: Timeout must be between 1 and 3600 seconds"
219
return
220
}
221
222
if (!password) {
223
// Prompt for password
224
print "Password: "
225
password = System.console()?.readPassword()?.toString()
226
if (!password) {
227
println "Error: Password is required"
228
return
229
}
230
}
231
232
println "Deploying to environment: $environment"
233
println "User: $user"
234
println "Timeout: ${timeout}s"
235
println "Retries: $retries"
236
println "Config: $configFile"
237
238
if (services) {
239
println "Services: ${services.join(', ')}"
240
}
241
if (excludePatterns) {
242
println "Exclude patterns: ${excludePatterns.join(', ')}"
243
}
244
```
245
246
### CliBuilder with Picocli
247
248
Alternative CLI builder using Picocli library for annotation-based configuration.
249
250
```groovy { .api }
251
/**
252
* Command-line interface builder using Picocli
253
*/
254
class CliBuilder {
255
/** Create CliBuilder with Picocli backend */
256
CliBuilder()
257
258
/** Create with usage text */
259
CliBuilder(String usage)
260
261
/** Parse arguments */
262
Object parse(String[] args)
263
264
/** Parse with command object */
265
<T> T parse(String[] args, Class<T> commandClass)
266
267
/** Set command specification */
268
void setSpec(Object spec)
269
270
/** Print usage information */
271
void usage()
272
273
/** Print version information */
274
void version()
275
}
276
```
277
278
**Usage Examples:**
279
280
```groovy
281
import groovy.cli.picocli.*
282
import picocli.CommandLine.*
283
284
// Using annotations for command specification
285
@Command(name = 'backup', description = 'Backup utility')
286
class BackupCommand {
287
@Option(names = ['-h', '--help'], usageHelp = true, description = 'Show help')
288
boolean help
289
290
@Option(names = ['-v', '--verbose'], description = 'Verbose output')
291
boolean verbose
292
293
@Option(names = ['-o', '--output'], paramLabel = 'DIR', description = 'Output directory')
294
String outputDir = './backup'
295
296
@Option(names = ['-c', '--compress'], description = 'Compress backup files')
297
boolean compress
298
299
@Option(names = ['-e', '--exclude'], paramLabel = 'PATTERN', description = 'Exclude patterns')
300
List<String> excludePatterns = []
301
302
@Parameters(paramLabel = 'SOURCE', description = 'Source directories to backup')
303
List<String> sources = []
304
305
@Spec CommandSpec spec // Injected by Picocli
306
307
void run() {
308
if (sources.isEmpty()) {
309
throw new ParameterException(spec.commandLine(), "At least one source directory must be specified")
310
}
311
312
if (verbose) {
313
println "Backup configuration:"
314
println " Sources: ${sources.join(', ')}"
315
println " Output: $outputDir"
316
println " Compress: $compress"
317
if (excludePatterns) {
318
println " Exclude: ${excludePatterns.join(', ')}"
319
}
320
}
321
322
// Perform backup
323
performBackup(sources, outputDir, compress, excludePatterns, verbose)
324
}
325
}
326
327
// Use the command
328
def cli = new CliBuilder()
329
def command = cli.parse(args, BackupCommand)
330
331
if (command) {
332
command.run()
333
}
334
```
335
336
### Subcommands and Command Hierarchies
337
338
Create complex CLI applications with subcommands.
339
340
```groovy
341
// Main command with subcommands
342
@Command(name = 'myapp',
343
description = 'Multi-purpose application',
344
subcommands = [UserCommand, ProjectCommand, ConfigCommand])
345
class MainCommand implements Runnable {
346
@Option(names = ['-h', '--help'], usageHelp = true)
347
boolean help
348
349
@Option(names = ['-v', '--version'], versionHelp = true)
350
boolean version
351
352
void run() {
353
println "Use 'myapp --help' for available commands"
354
}
355
}
356
357
// User management subcommand
358
@Command(name = 'user', description = 'User management commands')
359
class UserCommand {
360
@Command(name = 'create', description = 'Create a new user')
361
void create(
362
@Option(names = ['-n', '--name'], required = true) String name,
363
@Option(names = ['-e', '--email'], required = true) String email,
364
@Option(names = ['-r', '--role']) String role = 'user'
365
) {
366
println "Creating user: $name ($email) with role: $role"
367
// Implementation here
368
}
369
370
@Command(name = 'list', description = 'List all users')
371
void list(
372
@Option(names = ['-f', '--format']) String format = 'table',
373
@Option(names = ['-r', '--role']) String roleFilter
374
) {
375
println "Listing users (format: $format)"
376
if (roleFilter) {
377
println "Filtering by role: $roleFilter"
378
}
379
// Implementation here
380
}
381
382
@Command(name = 'delete', description = 'Delete a user')
383
void delete(@Parameters(paramLabel = 'USER_ID') String userId) {
384
println "Deleting user: $userId"
385
// Implementation here
386
}
387
}
388
389
// Project management subcommand
390
@Command(name = 'project', description = 'Project management commands')
391
class ProjectCommand {
392
@Command(name = 'init', description = 'Initialize a new project')
393
void init(
394
@Parameters(paramLabel = 'NAME') String name,
395
@Option(names = ['-t', '--template']) String template,
396
@Option(names = ['-d', '--directory']) String directory
397
) {
398
println "Initializing project: $name"
399
if (template) println "Using template: $template"
400
if (directory) println "In directory: $directory"
401
// Implementation here
402
}
403
404
@Command(name = 'build', description = 'Build the project')
405
void build(
406
@Option(names = ['-e', '--environment']) String env = 'development',
407
@Option(names = ['-c', '--clean']) boolean clean
408
) {
409
if (clean) println "Cleaning build artifacts"
410
println "Building project for environment: $env"
411
// Implementation here
412
}
413
}
414
415
// Usage
416
def commandLine = new CommandLine(new MainCommand())
417
commandLine.execute(args)
418
```
419
420
### Error Handling and Validation
421
422
Implement robust error handling and input validation.
423
424
```groovy
425
import picocli.CommandLine.*
426
427
class ValidatedCommand {
428
@Option(names = ['-p', '--port'], description = 'Server port (1024-65535)')
429
void setPort(int port) {
430
if (port < 1024 || port > 65535) {
431
throw new ParameterException(
432
CommandLine.this,
433
"Port must be between 1024 and 65535, got: $port"
434
)
435
}
436
this.port = port
437
}
438
private int port = 8080
439
440
@Option(names = ['-f', '--file'], description = 'Input file')
441
void setFile(File file) {
442
if (!file.exists()) {
443
throw new ParameterException(
444
CommandLine.this,
445
"File does not exist: ${file.absolutePath}"
446
)
447
}
448
if (!file.canRead()) {
449
throw new ParameterException(
450
CommandLine.this,
451
"Cannot read file: ${file.absolutePath}"
452
)
453
}
454
this.file = file
455
}
456
private File file
457
458
@Option(names = ['-e', '--email'], description = 'Email address')
459
void setEmail(String email) {
460
if (!email.matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
461
throw new ParameterException(
462
CommandLine.this,
463
"Invalid email format: $email"
464
)
465
}
466
this.email = email
467
}
468
private String email
469
470
void run() {
471
println "Port: $port"
472
println "File: ${file?.absolutePath}"
473
println "Email: $email"
474
}
475
}
476
477
// Custom exception handler
478
class CustomExceptionHandler implements IExceptionHandler2<List<Object>> {
479
List<Object> handleParseException(ParameterException ex, String[] args) {
480
System.err.println("Error: ${ex.message}")
481
ex.commandLine.usage(System.err)
482
return [1] // exit code
483
}
484
485
List<Object> handleExecutionException(ExecutionException ex, ParseResult parseResult) {
486
System.err.println("Execution failed: ${ex.cause?.message}")
487
return [2] // exit code
488
}
489
}
490
491
// Use with error handling
492
def commandLine = new CommandLine(new ValidatedCommand())
493
commandLine.setExceptionHandler(new CustomExceptionHandler())
494
int exitCode = commandLine.execute(args)
495
System.exit(exitCode)
496
```