Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax.
—
CLI building and option parsing utilities for creating robust command-line applications with support for various argument patterns and help text generation.
Command-line interface builder using Apache Commons CLI library for robust option parsing.
/**
* Command-line interface builder using Apache Commons CLI
*/
class CliBuilder {
/** Create CliBuilder with default settings */
CliBuilder()
/** Create CliBuilder with usage text */
CliBuilder(String usage)
/** Parse command-line arguments */
OptionAccessor parse(String[] args)
/** Parse arguments and handle errors */
OptionAccessor parse(String[] args, boolean stopAtNonOption)
/** Print usage information */
void usage()
/** Print usage to specific writer */
void usage(PrintWriter writer)
/** Print usage to specific writer with width */
void usage(PrintWriter writer, int width)
/** Set usage text */
void setUsage(String usage)
/** Set header text */
void setHeader(String header)
/** Set footer text */
void setFooter(String footer)
/** Set whether to stop at non-option */
void setStopAtNonOption(boolean stopAtNonOption)
/** Add option with short name */
void option(String shortOpt, String description)
/** Add option with long name */
void longOpt(String longOpt, String description)
/** Add option with argument */
void optionWithArg(String shortOpt, String longOpt, String description)
/** Add option with optional argument */
void optionWithOptionalArg(String shortOpt, String longOpt, String description)
}
/**
* Accessor for parsed command-line options
*/
class OptionAccessor {
/** Check if option was provided */
boolean hasOption(String option)
/** Get option value */
Object getOptionValue(String option)
/** Get option value with default */
Object getOptionValue(String option, Object defaultValue)
/** Get option values (for options that can appear multiple times) */
String[] getOptionValues(String option)
/** Get remaining arguments */
String[] getArgs()
/** Get arguments as list */
List<String> arguments()
}Usage Examples:
import groovy.cli.commons.*
// Basic CLI setup
def cli = new CliBuilder(usage: 'myapp [options] <files>')
cli.setHeader('My Application - File Processor')
cli.setFooter('For more information, visit: https://example.com')
// Define options
cli.h(longOpt: 'help', 'Show this help message')
cli.v(longOpt: 'verbose', 'Verbose output')
cli.q(longOpt: 'quiet', 'Quiet mode')
cli.o(longOpt: 'output', args: 1, argName: 'file', 'Output file')
cli.f(longOpt: 'format', args: 1, argName: 'format', 'Output format (xml|json|csv)')
cli.r(longOpt: 'recursive', 'Process directories recursively')
cli.n(longOpt: 'dry-run', 'Show what would be done without actually doing it')
// Parse arguments
def options = cli.parse(args)
if (!options) {
// Parsing failed
return
}
if (options.h) {
cli.usage()
return
}
// Access options
boolean verbose = options.v
boolean quiet = options.q
boolean recursive = options.r
boolean dryRun = options.n
String outputFile = options.o
String format = options.getOptionValue('f', 'json') // default to 'json'
// Get remaining arguments (files to process)
def files = options.arguments()
if (files.isEmpty()) {
println "Error: No input files specified"
cli.usage()
return
}
// Process based on options
if (verbose && !quiet) {
println "Processing ${files.size()} files"
println "Output format: $format"
if (outputFile) {
println "Output file: $outputFile"
}
if (recursive) {
println "Recursive processing enabled"
}
if (dryRun) {
println "DRY RUN MODE - no changes will be made"
}
}
files.each { file ->
if (verbose && !quiet) {
println "Processing: $file"
}
if (!dryRun) {
// Actually process the file
processFile(file, format, outputFile, recursive)
}
}Configure complex option patterns with validation and type conversion.
// Advanced CLI with multiple argument types
def cli = new CliBuilder(usage: 'deploy [options] <environment>')
// String options
cli.u(longOpt: 'user', args: 1, argName: 'username', 'Username for deployment')
cli.p(longOpt: 'password', args: 1, argName: 'password', 'Password (prompt if not provided)')
// Numeric options
cli.t(longOpt: 'timeout', args: 1, argName: 'seconds', type: Integer, 'Timeout in seconds (default: 300)')
cli.r(longOpt: 'retries', args: 1, argName: 'count', type: Integer, 'Number of retry attempts (default: 3)')
// Multiple values
cli.s(longOpt: 'service', args: Option.UNLIMITED_VALUES, argName: 'service', 'Services to deploy (can be specified multiple times)')
cli.e(longOpt: 'exclude', args: Option.UNLIMITED_VALUES, argName: 'pattern', 'Exclude patterns')
// Optional arguments
cli.c(longOpt: 'config', optionalArg: true, argName: 'file', 'Configuration file (default: config.yml)')
// Boolean flags
cli.f(longOpt: 'force', 'Force deployment even if validation fails')
cli.v(longOpt: 'verbose', 'Verbose output')
cli.q(longOpt: 'quiet', 'Quiet mode')
def options = cli.parse(args)
if (!options || options.arguments().isEmpty()) {
println "Error: Environment must be specified"
cli.usage()
return
}
// Extract values with defaults and validation
String environment = options.arguments()[0]
String user = options.u
String password = options.p
Integer timeout = options.t ? options.t as Integer : 300
Integer retries = options.r ? options.r as Integer : 3
String configFile = options.c ?: 'config.yml'
// Handle multiple values
List<String> services = options.ss ?: [] // Note: multiple values accessed with repeated short option
List<String> excludePatterns = options.es ?: []
// Validation
if (!user) {
println "Error: Username is required"
cli.usage()
return
}
if (timeout < 1 || timeout > 3600) {
println "Error: Timeout must be between 1 and 3600 seconds"
return
}
if (!password) {
// Prompt for password
print "Password: "
password = System.console()?.readPassword()?.toString()
if (!password) {
println "Error: Password is required"
return
}
}
println "Deploying to environment: $environment"
println "User: $user"
println "Timeout: ${timeout}s"
println "Retries: $retries"
println "Config: $configFile"
if (services) {
println "Services: ${services.join(', ')}"
}
if (excludePatterns) {
println "Exclude patterns: ${excludePatterns.join(', ')}"
}Alternative CLI builder using Picocli library for annotation-based configuration.
/**
* Command-line interface builder using Picocli
*/
class CliBuilder {
/** Create CliBuilder with Picocli backend */
CliBuilder()
/** Create with usage text */
CliBuilder(String usage)
/** Parse arguments */
Object parse(String[] args)
/** Parse with command object */
<T> T parse(String[] args, Class<T> commandClass)
/** Set command specification */
void setSpec(Object spec)
/** Print usage information */
void usage()
/** Print version information */
void version()
}Usage Examples:
import groovy.cli.picocli.*
import picocli.CommandLine.*
// Using annotations for command specification
@Command(name = 'backup', description = 'Backup utility')
class BackupCommand {
@Option(names = ['-h', '--help'], usageHelp = true, description = 'Show help')
boolean help
@Option(names = ['-v', '--verbose'], description = 'Verbose output')
boolean verbose
@Option(names = ['-o', '--output'], paramLabel = 'DIR', description = 'Output directory')
String outputDir = './backup'
@Option(names = ['-c', '--compress'], description = 'Compress backup files')
boolean compress
@Option(names = ['-e', '--exclude'], paramLabel = 'PATTERN', description = 'Exclude patterns')
List<String> excludePatterns = []
@Parameters(paramLabel = 'SOURCE', description = 'Source directories to backup')
List<String> sources = []
@Spec CommandSpec spec // Injected by Picocli
void run() {
if (sources.isEmpty()) {
throw new ParameterException(spec.commandLine(), "At least one source directory must be specified")
}
if (verbose) {
println "Backup configuration:"
println " Sources: ${sources.join(', ')}"
println " Output: $outputDir"
println " Compress: $compress"
if (excludePatterns) {
println " Exclude: ${excludePatterns.join(', ')}"
}
}
// Perform backup
performBackup(sources, outputDir, compress, excludePatterns, verbose)
}
}
// Use the command
def cli = new CliBuilder()
def command = cli.parse(args, BackupCommand)
if (command) {
command.run()
}Create complex CLI applications with subcommands.
// Main command with subcommands
@Command(name = 'myapp',
description = 'Multi-purpose application',
subcommands = [UserCommand, ProjectCommand, ConfigCommand])
class MainCommand implements Runnable {
@Option(names = ['-h', '--help'], usageHelp = true)
boolean help
@Option(names = ['-v', '--version'], versionHelp = true)
boolean version
void run() {
println "Use 'myapp --help' for available commands"
}
}
// User management subcommand
@Command(name = 'user', description = 'User management commands')
class UserCommand {
@Command(name = 'create', description = 'Create a new user')
void create(
@Option(names = ['-n', '--name'], required = true) String name,
@Option(names = ['-e', '--email'], required = true) String email,
@Option(names = ['-r', '--role']) String role = 'user'
) {
println "Creating user: $name ($email) with role: $role"
// Implementation here
}
@Command(name = 'list', description = 'List all users')
void list(
@Option(names = ['-f', '--format']) String format = 'table',
@Option(names = ['-r', '--role']) String roleFilter
) {
println "Listing users (format: $format)"
if (roleFilter) {
println "Filtering by role: $roleFilter"
}
// Implementation here
}
@Command(name = 'delete', description = 'Delete a user')
void delete(@Parameters(paramLabel = 'USER_ID') String userId) {
println "Deleting user: $userId"
// Implementation here
}
}
// Project management subcommand
@Command(name = 'project', description = 'Project management commands')
class ProjectCommand {
@Command(name = 'init', description = 'Initialize a new project')
void init(
@Parameters(paramLabel = 'NAME') String name,
@Option(names = ['-t', '--template']) String template,
@Option(names = ['-d', '--directory']) String directory
) {
println "Initializing project: $name"
if (template) println "Using template: $template"
if (directory) println "In directory: $directory"
// Implementation here
}
@Command(name = 'build', description = 'Build the project')
void build(
@Option(names = ['-e', '--environment']) String env = 'development',
@Option(names = ['-c', '--clean']) boolean clean
) {
if (clean) println "Cleaning build artifacts"
println "Building project for environment: $env"
// Implementation here
}
}
// Usage
def commandLine = new CommandLine(new MainCommand())
commandLine.execute(args)Implement robust error handling and input validation.
import picocli.CommandLine.*
class ValidatedCommand {
@Option(names = ['-p', '--port'], description = 'Server port (1024-65535)')
void setPort(int port) {
if (port < 1024 || port > 65535) {
throw new ParameterException(
CommandLine.this,
"Port must be between 1024 and 65535, got: $port"
)
}
this.port = port
}
private int port = 8080
@Option(names = ['-f', '--file'], description = 'Input file')
void setFile(File file) {
if (!file.exists()) {
throw new ParameterException(
CommandLine.this,
"File does not exist: ${file.absolutePath}"
)
}
if (!file.canRead()) {
throw new ParameterException(
CommandLine.this,
"Cannot read file: ${file.absolutePath}"
)
}
this.file = file
}
private File file
@Option(names = ['-e', '--email'], description = 'Email address')
void setEmail(String email) {
if (!email.matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
throw new ParameterException(
CommandLine.this,
"Invalid email format: $email"
)
}
this.email = email
}
private String email
void run() {
println "Port: $port"
println "File: ${file?.absolutePath}"
println "Email: $email"
}
}
// Custom exception handler
class CustomExceptionHandler implements IExceptionHandler2<List<Object>> {
List<Object> handleParseException(ParameterException ex, String[] args) {
System.err.println("Error: ${ex.message}")
ex.commandLine.usage(System.err)
return [1] // exit code
}
List<Object> handleExecutionException(ExecutionException ex, ParseResult parseResult) {
System.err.println("Execution failed: ${ex.cause?.message}")
return [2] // exit code
}
}
// Use with error handling
def commandLine = new CommandLine(new ValidatedCommand())
commandLine.setExceptionHandler(new CustomExceptionHandler())
int exitCode = commandLine.execute(args)
System.exit(exitCode)Install with Tessl CLI
npx tessl i tessl/maven-org-apache-groovy--groovy