0
# Command Line Interface
1
2
Groovy provides powerful command-line argument parsing capabilities through CliBuilder, which integrates with PicoCLI for creating robust CLI applications.
3
4
## CliBuilder
5
6
### Basic CLI Builder
7
8
```groovy { .api }
9
class CliBuilder {
10
CliBuilder()
11
CliBuilder(String usage)
12
CliBuilder(PrintWriter writer)
13
CliBuilder(PrintWriter writer, String usage)
14
15
void setUsage(String usage)
16
String getUsage()
17
void setHeader(String header)
18
void setFooter(String footer)
19
void setWidth(int width)
20
void setFormatter(HelpFormatter formatter)
21
22
OptionAccessor parse(String[] args)
23
OptionAccessor parse(List<String> args)
24
25
void usage()
26
String usage()
27
28
// Option definition methods
29
def opt(String opt, String description)
30
def opt(String opt, String longOpt, String description)
31
def opt(String opt, String longOpt, String description, boolean required)
32
def opt(String opt, boolean hasArg, String description)
33
def opt(String opt, String longOpt, boolean hasArg, String description)
34
def opt(Map args, String description)
35
}
36
```
37
38
### OptionAccessor
39
40
Interface for accessing parsed command-line options.
41
42
```groovy { .api }
43
interface OptionAccessor {
44
boolean hasOption(String opt)
45
boolean hasOption(char opt)
46
Object getProperty(String property)
47
48
String getOptionValue(String opt)
49
String getOptionValue(char opt)
50
String getOptionValue(String opt, String defaultValue)
51
String[] getOptionValues(String opt)
52
53
List arguments()
54
String[] getArgs()
55
}
56
```
57
58
## Basic Usage Examples
59
60
### Simple CLI Application
61
62
```groovy
63
import groovy.cli.picocli.CliBuilder
64
65
def cli = new CliBuilder(usage: 'myapp [options] <files>')
66
cli.header = 'Process files with various options'
67
cli.footer = 'Example: myapp -v -o output.txt input1.txt input2.txt'
68
69
// Define options
70
cli.h(longOpt: 'help', 'Show usage information')
71
cli.v(longOpt: 'verbose', 'Enable verbose output')
72
cli.o(longOpt: 'output', args: 1, argName: 'file', 'Output file')
73
cli.n(longOpt: 'number', args: 1, argName: 'num', type: Integer, 'Number of iterations')
74
cli.f(longOpt: 'force', 'Force overwrite existing files')
75
76
// Parse arguments
77
def options = cli.parse(args)
78
79
if (!options) {
80
System.exit(1)
81
}
82
83
if (options.h) {
84
cli.usage()
85
System.exit(0)
86
}
87
88
// Access options
89
if (options.v) {
90
println "Verbose mode enabled"
91
}
92
93
def outputFile = options.o ?: 'default-output.txt'
94
def iterations = options.n ?: 1
95
def forceOverwrite = options.f
96
97
println "Output file: $outputFile"
98
println "Iterations: $iterations"
99
println "Force overwrite: $forceOverwrite"
100
101
// Process remaining arguments (files)
102
def files = options.arguments()
103
files.each { file ->
104
println "Processing file: $file"
105
}
106
```
107
108
### Advanced Option Types
109
110
```groovy
111
import groovy.cli.picocli.CliBuilder
112
113
def cli = new CliBuilder()
114
115
// Different option types
116
cli.s(longOpt: 'string', args: 1, 'String option')
117
cli.i(longOpt: 'integer', args: 1, type: Integer, 'Integer option')
118
cli.d(longOpt: 'double', args: 1, type: Double, 'Double option')
119
cli.b(longOpt: 'boolean', type: Boolean, 'Boolean option')
120
cli.f(longOpt: 'file', args: 1, type: File, 'File option')
121
cli.u(longOpt: 'url', args: 1, type: URL, 'URL option')
122
123
// Multiple values
124
cli.m(longOpt: 'multiple', args: '+', 'Multiple string values')
125
cli.l(longOpt: 'list', args: '*', 'Optional multiple values')
126
127
// Required options
128
cli.r(longOpt: 'required', args: 1, required: true, 'Required option')
129
130
def options = cli.parse([
131
'--string', 'hello',
132
'--integer', '42',
133
'--double', '3.14',
134
'--boolean', 'true',
135
'--file', '/path/to/file.txt',
136
'--multiple', 'value1', 'value2', 'value3',
137
'--required', 'must-have'
138
])
139
140
if (options) {
141
println "String: ${options.s}"
142
println "Integer: ${options.i}"
143
println "Double: ${options.d}"
144
println "Boolean: ${options.b}"
145
println "File: ${options.f}"
146
println "Multiple: ${options.ms}" // Returns array
147
println "Required: ${options.r}"
148
}
149
```
150
151
### Validation and Error Handling
152
153
```groovy
154
import groovy.cli.picocli.CliBuilder
155
156
def cli = new CliBuilder(usage: 'validator [options]')
157
158
cli.p(longOpt: 'port', args: 1, type: Integer, 'Port number (1-65535)')
159
cli.e(longOpt: 'email', args: 1, 'Email address')
160
cli.d(longOpt: 'date', args: 1, type: Date, 'Date in format yyyy-MM-dd')
161
162
def options = cli.parse(args)
163
164
if (!options) {
165
println "Failed to parse command line options"
166
System.exit(1)
167
}
168
169
// Custom validation
170
try {
171
if (options.p && (options.p < 1 || options.p > 65535)) {
172
throw new IllegalArgumentException("Port must be between 1 and 65535")
173
}
174
175
if (options.e && !options.e.matches(/[\w._%+-]+@[\w.-]+\.[A-Za-z]{2,}/)) {
176
throw new IllegalArgumentException("Invalid email format")
177
}
178
179
println "All validations passed"
180
181
} catch (Exception e) {
182
println "Validation error: ${e.message}"
183
cli.usage()
184
System.exit(1)
185
}
186
```
187
188
## Advanced Features
189
190
### Subcommands
191
192
```groovy
193
import groovy.cli.picocli.CliBuilder
194
195
class GitLikeApp {
196
static void main(String[] args) {
197
if (args.length == 0) {
198
printUsage()
199
return
200
}
201
202
def command = args[0]
203
def commandArgs = args.length > 1 ? args[1..-1] : []
204
205
switch (command) {
206
case 'init':
207
handleInit(commandArgs)
208
break
209
case 'add':
210
handleAdd(commandArgs)
211
break
212
case 'commit':
213
handleCommit(commandArgs)
214
break
215
default:
216
println "Unknown command: $command"
217
printUsage()
218
}
219
}
220
221
static void handleInit(String[] args) {
222
def cli = new CliBuilder(usage: 'myapp init [options]')
223
cli.b(longOpt: 'bare', 'Create bare repository')
224
cli.q(longOpt: 'quiet', 'Suppress output')
225
226
def options = cli.parse(args)
227
if (!options) return
228
229
println "Initializing repository..."
230
if (options.b) println "Creating bare repository"
231
if (!options.q) println "Repository initialized"
232
}
233
234
static void handleAdd(String[] args) {
235
def cli = new CliBuilder(usage: 'myapp add [options] <files>')
236
cli.A(longOpt: 'all', 'Add all files')
237
cli.v(longOpt: 'verbose', 'Verbose output')
238
239
def options = cli.parse(args)
240
if (!options) return
241
242
if (options.A) {
243
println "Adding all files"
244
} else {
245
options.arguments().each { file ->
246
println "Adding file: $file"
247
}
248
}
249
}
250
251
static void handleCommit(String[] args) {
252
def cli = new CliBuilder(usage: 'myapp commit [options]')
253
cli.m(longOpt: 'message', args: 1, required: true, 'Commit message')
254
cli.a(longOpt: 'all', 'Commit all changes')
255
256
def options = cli.parse(args)
257
if (!options) return
258
259
println "Committing with message: ${options.m}"
260
if (options.a) println "Including all changes"
261
}
262
263
static void printUsage() {
264
println """
265
Usage: myapp <command> [options]
266
267
Commands:
268
init Initialize a new repository
269
add Add files to staging area
270
commit Create a new commit
271
272
Use 'myapp <command> --help' for command-specific options.
273
"""
274
}
275
}
276
```
277
278
### Configuration File Integration
279
280
```groovy
281
import groovy.cli.picocli.CliBuilder
282
import groovy.json.JsonSlurper
283
284
class ConfigurableApp {
285
def config = [:]
286
287
void loadConfig(String configFile) {
288
def file = new File(configFile)
289
if (file.exists()) {
290
def slurper = new JsonSlurper()
291
config = slurper.parse(file)
292
}
293
}
294
295
void run(String[] args) {
296
def cli = new CliBuilder(usage: 'app [options]')
297
cli.c(longOpt: 'config', args: 1, 'Configuration file')
298
cli.h(longOpt: 'host', args: 1, 'Server host')
299
cli.p(longOpt: 'port', args: 1, type: Integer, 'Server port')
300
cli.v(longOpt: 'verbose', 'Verbose output')
301
302
def options = cli.parse(args)
303
if (!options) return
304
305
// Load configuration file if specified
306
if (options.c) {
307
loadConfig(options.c)
308
}
309
310
// Command line options override config file
311
def host = options.h ?: config.host ?: 'localhost'
312
def port = options.p ?: config.port ?: 8080
313
def verbose = options.v ?: config.verbose ?: false
314
315
println "Host: $host"
316
println "Port: $port"
317
println "Verbose: $verbose"
318
319
startServer(host, port, verbose)
320
}
321
322
void startServer(String host, int port, boolean verbose) {
323
if (verbose) {
324
println "Starting server in verbose mode..."
325
}
326
println "Server started on $host:$port"
327
}
328
}
329
```
330
331
### Interactive CLI
332
333
```groovy
334
import groovy.cli.picocli.CliBuilder
335
import java.util.Scanner
336
337
class InteractiveCLI {
338
private Scanner scanner = new Scanner(System.in)
339
private boolean running = true
340
341
void start() {
342
println "Interactive CLI started. Type 'help' for commands."
343
344
while (running) {
345
print "> "
346
def input = scanner.nextLine().trim()
347
if (input) {
348
processCommand(input.split(/\s+/))
349
}
350
}
351
}
352
353
void processCommand(String[] args) {
354
def command = args[0]
355
def commandArgs = args.length > 1 ? args[1..-1] : []
356
357
switch (command) {
358
case 'help':
359
showHelp()
360
break
361
case 'echo':
362
handleEcho(commandArgs)
363
break
364
case 'calc':
365
handleCalculator(commandArgs)
366
break
367
case 'exit':
368
case 'quit':
369
running = false
370
println "Goodbye!"
371
break
372
default:
373
println "Unknown command: $command. Type 'help' for available commands."
374
}
375
}
376
377
void handleEcho(String[] args) {
378
def cli = new CliBuilder(usage: 'echo [options] <text>')
379
cli.n('No newline')
380
cli.u('Uppercase')
381
382
def options = cli.parse(args)
383
if (!options) return
384
385
def text = options.arguments().join(' ')
386
if (options.u) text = text.toUpperCase()
387
388
if (options.n) {
389
print text
390
} else {
391
println text
392
}
393
}
394
395
void handleCalculator(String[] args) {
396
def cli = new CliBuilder(usage: 'calc <operation> <num1> <num2>')
397
398
def options = cli.parse(args)
399
if (!options || options.arguments().size() != 3) {
400
println "Usage: calc <add|sub|mul|div> <number1> <number2>"
401
return
402
}
403
404
try {
405
def operation = options.arguments()[0]
406
def num1 = Double.parseDouble(options.arguments()[1])
407
def num2 = Double.parseDouble(options.arguments()[2])
408
409
def result = switch (operation) {
410
case 'add' -> num1 + num2
411
case 'sub' -> num1 - num2
412
case 'mul' -> num1 * num2
413
case 'div' -> num2 != 0 ? num1 / num2 : { throw new ArithmeticException("Division by zero") }()
414
default -> { println "Unknown operation: $operation"; return }
415
}
416
417
println "Result: $result"
418
419
} catch (NumberFormatException e) {
420
println "Invalid number format"
421
} catch (ArithmeticException e) {
422
println "Math error: ${e.message}"
423
}
424
}
425
426
void showHelp() {
427
println """
428
Available commands:
429
help Show this help message
430
echo [options] <text> Echo text with options
431
-n No newline
432
-u Uppercase
433
calc <op> <n1> <n2> Calculator (add, sub, mul, div)
434
exit, quit Exit the program
435
"""
436
}
437
}
438
439
// Usage
440
new InteractiveCLI().start()
441
```
442
443
## Best Practices
444
445
### Error Handling and User Experience
446
447
```groovy
448
import groovy.cli.picocli.CliBuilder
449
450
class RobustCLI {
451
static void main(String[] args) {
452
try {
453
def app = new RobustCLI()
454
app.run(args)
455
} catch (Exception e) {
456
System.err.println("Unexpected error: ${e.message}")
457
if (System.getProperty('debug')) {
458
e.printStackTrace()
459
}
460
System.exit(1)
461
}
462
}
463
464
void run(String[] args) {
465
def cli = new CliBuilder(usage: 'robustapp [options] <files>')
466
cli.width = 120
467
cli.header = 'A robust CLI application example'
468
cli.footer = '''
469
Examples:
470
robustapp -v file1.txt file2.txt
471
robustapp --output=result.txt --format=json input.csv
472
'''
473
474
// Define comprehensive options
475
cli.h(longOpt: 'help', 'Show this help message')
476
cli.v(longOpt: 'verbose', 'Enable verbose output')
477
cli.q(longOpt: 'quiet', 'Suppress output')
478
cli.o(longOpt: 'output', args: 1, argName: 'file', 'Output file')
479
cli.f(longOpt: 'format', args: 1, argName: 'fmt', 'Output format (json|xml|csv)')
480
cli._(longOpt: 'version', 'Show version information')
481
482
def options = cli.parse(args)
483
484
// Handle parse errors
485
if (!options) {
486
println "Use --help for usage information"
487
System.exit(1)
488
}
489
490
// Handle help and version
491
if (options.h) {
492
cli.usage()
493
return
494
}
495
496
if (options.version) {
497
println "RobustApp version 1.0.0"
498
return
499
}
500
501
// Validate conflicting options
502
if (options.v && options.q) {
503
System.err.println("Error: --verbose and --quiet cannot be used together")
504
System.exit(1)
505
}
506
507
// Validate required arguments
508
if (!options.arguments()) {
509
System.err.println("Error: At least one input file is required")
510
cli.usage()
511
System.exit(1)
512
}
513
514
// Validate format option
515
def validFormats = ['json', 'xml', 'csv']
516
if (options.f && !validFormats.contains(options.f)) {
517
System.err.println("Error: Invalid format '${options.f}'. Valid formats: ${validFormats.join(', ')}")
518
System.exit(1)
519
}
520
521
// Process with validated options
522
processFiles(options)
523
}
524
525
void processFiles(options) {
526
def verbose = options.v && !options.q
527
def outputFile = options.o ?: 'stdout'
528
def format = options.f ?: 'json'
529
530
if (verbose) {
531
println "Processing ${options.arguments().size()} files"
532
println "Output: $outputFile"
533
println "Format: $format"
534
}
535
536
options.arguments().each { file ->
537
if (verbose) println "Processing: $file"
538
// Process file...
539
}
540
541
if (!options.q) {
542
println "Processing completed successfully"
543
}
544
}
545
}
546
```