Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 105 additions & 10 deletions libs/cli/src/main/java/org/elasticsearch/cli/Command.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,44 @@ public abstract class Command implements Closeable {
.availableUnless(silentOption);

/**
* Construct the command with the specified command description and runnable to execute before main is invoked.
* @param description the command description
* Constructs the command with the specified command description.
*
* @param description the command description to be displayed in help output
*
* <p><b>Usage Example:</b></p>
* <pre>{@code
* public class MyCommand extends Command {
* public MyCommand() {
* super("Performs custom processing");
* }
* }
* }</pre>
*/
public Command(final String description) {
this.description = description;
}

/** Parses options for this command from args and executes it. */
/**
* Parses command-line options and executes this command with proper error handling.
*
* <p>This is the main entry point for command execution. It handles parsing
* of command-line arguments, error handling, and returning appropriate exit codes.
* All exceptions are caught and converted to appropriate exit codes.
*
* @param args the command-line arguments to parse
* @param terminal the terminal for input/output operations
* @param processInfo information about the current process (system properties, environment variables, etc.)
* @return the exit code (0 for success, non-zero for errors as defined in {@link ExitCodes})
* @throws IOException if an I/O error occurs during command execution
*
* <p><b>Usage Example:</b></p>
* <pre>{@code
* Command cmd = new MyCommand();
* Terminal terminal = Terminal.DEFAULT;
* ProcessInfo processInfo = ProcessInfo.fromSystem();
* int exitCode = cmd.main(new String[]{"--verbose"}, terminal, processInfo);
* }</pre>
*/
public final int main(String[] args, Terminal terminal, ProcessInfo processInfo) throws IOException {
try {
mainWithoutErrorHandling(args, terminal, processInfo);
Expand Down Expand Up @@ -76,7 +105,16 @@ public final int main(String[] args, Terminal terminal, ProcessInfo processInfo)
}

/**
* Executes the command, but all errors are thrown.
* Executes the command without error handling, allowing all exceptions to propagate.
*
* <p>This method parses options, handles help and verbosity flags, and delegates
* to {@link #execute(Terminal, OptionSet, ProcessInfo)}. Unlike {@link #main(String[], Terminal, ProcessInfo)},
* this method does not catch exceptions, allowing callers to handle them.
*
* @param args the command-line arguments to parse
* @param terminal the terminal for input/output operations
* @param processInfo information about the current process
* @throws Exception if any error occurs during command execution
*/
protected void mainWithoutErrorHandling(String[] args, Terminal terminal, ProcessInfo processInfo) throws Exception {
final OptionSet options = parseOptions(args);
Expand All @@ -102,9 +140,16 @@ protected void mainWithoutErrorHandling(String[] args, Terminal terminal, Proces
}

/**
* Parse command line arguments for this command.
* @param args The string arguments passed to the command
* @return A set of parsed options
* Parses command-line arguments for this command using the configured option parser.
*
* @param args the string arguments passed to the command
* @return a set of parsed options
* @throws joptsimple.OptionException if the arguments cannot be parsed
*
* <p><b>Usage Example:</b></p>
* <pre>{@code
* OptionSet options = parseOptions(new String[]{"--verbose", "input.txt"});
* }</pre>
*/
public OptionSet parseOptions(String[] args) {
return parser.parse(args);
Expand All @@ -126,27 +171,77 @@ private void printHelp(Terminal terminal, boolean toStdError) throws IOException
}
}

/** Prints additional help information, specific to the command */
/**
* Prints additional help information specific to this command.
*
* <p>Subclasses can override this method to provide command-specific help text
* that will be displayed when the user requests help via the -h or --help option.
*
* @param terminal the terminal to write help output to
*/
protected void printAdditionalHelp(Terminal terminal) {}

/**
* Prints a user exception message to the terminal's error stream.
*
* <p>Subclasses can override this method to customize how user exceptions are displayed.
*
* @param terminal the terminal to write error output to
* @param e the user exception to print
*/
protected void printUserException(Terminal terminal, UserException e) {
if (e.getMessage() != null) {
terminal.errorPrintln("");
terminal.errorPrintln(Terminal.Verbosity.SILENT, "ERROR: " + e.getMessage() + ", with exit code " + e.exitCode);
}
}

/**
* Exits the JVM with the specified status code.
*
* <p>This method calls {@link System#exit(int)} and should be used sparingly,
* typically only after {@link #main(String[], Terminal, ProcessInfo)} has completed.
*
* @param status the exit status code (0 for success, non-zero for errors)
*/
@SuppressForbidden(reason = "Allowed to exit explicitly from #main()")
protected static void exit(int status) {
System.exit(status);
}

/**
* Executes this command.
* Executes the core logic of this command.
*
* Any runtime user errors (like an input file that does not exist), should throw a {@link UserException}. */
* <p>Subclasses must implement this method to provide command-specific functionality.
* This method is called by {@link #mainWithoutErrorHandling(String[], Terminal, ProcessInfo)}
* after options have been parsed and help/verbosity flags processed.
*
* @param terminal the terminal for input/output operations
* @param options the parsed command-line options
* @param processInfo information about the current process
* @throws Exception if any error occurs during execution
* @throws UserException for user-correctable errors (e.g., invalid input file)
*
* <p><b>Usage Example:</b></p>
* <pre>{@code
* @Override
* protected void execute(Terminal terminal, OptionSet options, ProcessInfo processInfo) throws Exception {
* String input = options.valueOf(inputOption);
* terminal.println("Processing: " + input);
* // ... perform command logic ...
* }
* }</pre>
*/
protected abstract void execute(Terminal terminal, OptionSet options, ProcessInfo processInfo) throws Exception;

/**
* Closes this command and releases any resources.
*
* <p>The default implementation does nothing. Subclasses should override this method
* to release any resources they have acquired.
*
* @throws IOException if an I/O error occurs while closing resources
*/
@Override
public void close() throws IOException {

Expand Down
68 changes: 51 additions & 17 deletions libs/cli/src/main/java/org/elasticsearch/cli/ExitCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,60 @@
package org.elasticsearch.cli;

/**
* POSIX exit codes.
* Standard POSIX exit codes for command-line tools.
*
* <p>These exit codes follow POSIX conventions and are used by CLI commands
* to indicate the result of their execution. These values are part of the public
* API and may be used in scripts, so they should not be changed.
*
* <p><b>Warning:</b> Do not modify these values as they may be used in external scripts
* where usages are not tracked by the IDE.
*/
public class ExitCodes {
// please be extra careful when changing these as the values might be used in scripts,
// usages of which are not tracked by the IDE
/** Successful completion (exit code 0). */
public static final int OK = 0;
public static final int USAGE = 64; // command line usage error
public static final int DATA_ERROR = 65; // data format error
public static final int NO_INPUT = 66; // cannot open input
public static final int NO_USER = 67; // addressee unknown
public static final int NO_HOST = 68; // host name unknown
public static final int UNAVAILABLE = 69; // service unavailable
public static final int CODE_ERROR = 70; // internal software error
public static final int CANT_CREATE = 73; // can't create (user) output file
public static final int IO_ERROR = 74; // input/output error
public static final int TEMP_FAILURE = 75; // temp failure; user is invited to retry
public static final int PROTOCOL = 76; // remote error in protocol
public static final int NOPERM = 77; // permission denied
public static final int CONFIG = 78; // configuration error
public static final int NOOP = 80; // nothing to do

/** Command line usage error (exit code 64). */
public static final int USAGE = 64;

/** Data format error (exit code 65). */
public static final int DATA_ERROR = 65;

/** Cannot open input (exit code 66). */
public static final int NO_INPUT = 66;

/** Addressee unknown (exit code 67). */
public static final int NO_USER = 67;

/** Host name unknown (exit code 68). */
public static final int NO_HOST = 68;

/** Service unavailable (exit code 69). */
public static final int UNAVAILABLE = 69;

/** Internal software error (exit code 70). */
public static final int CODE_ERROR = 70;

/** Can't create (user) output file (exit code 73). */
public static final int CANT_CREATE = 73;

/** Input/output error (exit code 74). */
public static final int IO_ERROR = 74;

/** Temporary failure; user is invited to retry (exit code 75). */
public static final int TEMP_FAILURE = 75;

/** Remote error in protocol (exit code 76). */
public static final int PROTOCOL = 76;

/** Permission denied (exit code 77). */
public static final int NOPERM = 77;

/** Configuration error (exit code 78). */
public static final int CONFIG = 78;

/** Nothing to do (exit code 80). */
public static final int NOOP = 80;

private ExitCodes() { /* no instance, just constants */ }
}
38 changes: 36 additions & 2 deletions libs/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,20 @@ public class MultiCommand extends Command {
private final OptionSpec<KeyValuePair> settingOption;

/**
* Construct the multi-command with the specified command description and runnable to execute before main is invoked.
* @param description the multi-command description
* Constructs a multi-command with the specified description.
*
* <p>A MultiCommand is a CLI tool that contains multiple sub-commands, each
* represented by a separate {@link Command} instance. The user specifies which
* sub-command to run as the first argument.
*
* @param description the multi-command description to be displayed in help output
*
* <p><b>Usage Example:</b></p>
* <pre>{@code
* MultiCommand tool = new MultiCommand("Elasticsearch administration tool");
* tool.subcommands.put("index", new IndexCommand());
* tool.subcommands.put("cluster", new ClusterCommand());
* }</pre>
*/
public MultiCommand(final String description) {
super(description);
Expand Down Expand Up @@ -70,6 +81,21 @@ private void printSubCommandList(Consumer<String> println) {
println.accept("");
}

/**
* Executes the appropriate sub-command based on the first command-line argument.
*
* <p>This method parses the first non-option argument to determine which sub-command
* to execute, then delegates to that sub-command's {@link Command#mainWithoutErrorHandling(String[], Terminal, ProcessInfo)}
* method.
*
* @param terminal the terminal for input/output operations
* @param options the parsed command-line options
* @param processInfo information about the current process
* @throws Exception if an error occurs during sub-command execution
* @throws MissingCommandException if no sub-command name is provided
* @throws UserException if the specified sub-command does not exist
* @throws IllegalStateException if no sub-commands have been configured
*/
@Override
protected void execute(Terminal terminal, OptionSet options, ProcessInfo processInfo) throws Exception {
if (subcommands.isEmpty()) {
Expand All @@ -95,6 +121,14 @@ protected void execute(Terminal terminal, OptionSet options, ProcessInfo process
subcommand.mainWithoutErrorHandling(args.toArray(new String[0]), terminal, processInfo);
}

/**
* Closes this multi-command and all of its sub-commands.
*
* <p>This method iterates through all registered sub-commands and closes each one,
* ensuring proper resource cleanup.
*
* @throws IOException if an I/O error occurs while closing any sub-command
*/
@Override
public void close() throws IOException {
IOUtils.close(subcommands.values());
Expand Down
Loading