org.elasticsearch.common.cli.CliTool Maven / Gradle / Ivy
The newest version!
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.cli;
import com.google.common.base.Preconditions;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
import java.io.IOException;
import java.util.Locale;
import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
/**
* A base class for command-line interface tool.
*
* Two modes are supported:
*
* - Single command mode. The tool exposes a single command that can potentially accept arguments (eg. CLI options).
* - Multi command mode. The tool support multiple commands, each for different tasks, each potentially accepts arguments.
*
* In a multi-command mode. The first argument must be the command name. For example, the plugin manager
* can be seen as a multi-command tool with two possible commands: install and uninstall
*
* The tool is configured using a {@link CliToolConfig} which encapsulates the tool's commands and their
* potential options. The tool also comes with out of the box simple help support (the -h/--help option is
* automatically handled) where the help text is configured in a dedicated *.help files located in the same package
* as the tool.
*/
public abstract class CliTool {
// based on sysexits.h
public static enum ExitStatus {
OK(0),
OK_AND_EXIT(0),
USAGE(64), /* command line usage error */
DATA_ERROR(65), /* data format error */
NO_INPUT(66), /* cannot open input */
NO_USER(67), /* addressee unknown */
NO_HOST(68), /* host name unknown */
UNAVAILABLE(69), /* service unavailable */
CODE_ERROR(70), /* internal software error */
CANT_CREATE(73), /* can't create (user) output file */
IO_ERROR(74), /* input/output error */
TEMP_FAILURE(75), /* temp failure; user is invited to retry */
PROTOCOL(76), /* remote error in protocol */
NOPERM(77), /* permission denied */
CONFIG(78); /* configuration error */
final int status;
private ExitStatus(int status) {
this.status = status;
}
public int status() {
return status;
}
public static ExitStatus fromStatus(int status) {
for (ExitStatus exitStatus : values()) {
if (exitStatus.status() == status) {
return exitStatus;
}
}
return null;
}
}
protected final Terminal terminal;
protected final Environment env;
protected final Settings settings;
private final CliToolConfig config;
protected CliTool(CliToolConfig config) {
this(config, Terminal.DEFAULT);
}
protected CliTool(CliToolConfig config, Terminal terminal) {
Preconditions.checkArgument(config.cmds().size() != 0, "At least one command must be configured");
this.config = config;
this.terminal = terminal;
env = InternalSettingsPreparer.prepareEnvironment(EMPTY_SETTINGS, terminal);
settings = env.settings();
}
public final ExitStatus execute(String... args) {
// first lets see if the user requests tool help. We're doing it only if
// this is a multi-command tool. If it's a single command tool, the -h/--help
// option will be taken care of on the command level
if (!config.isSingle() && args.length > 0 && (args[0].equals("-h") || args[0].equals("--help"))) {
config.printUsage(terminal);
return ExitStatus.OK_AND_EXIT;
}
CliToolConfig.Cmd cmd;
if (config.isSingle()) {
cmd = config.single();
} else {
if (args.length == 0) {
terminal.printError("command not specified");
config.printUsage(terminal);
return ExitStatus.USAGE;
}
String cmdName = args[0];
cmd = config.cmd(cmdName);
if (cmd == null) {
terminal.printError("unknown command [%s]. Use [-h] option to list available commands", cmdName);
return ExitStatus.USAGE;
}
// we now remove the command name from the args
if (args.length == 1) {
args = new String[0];
} else {
String[] cmdArgs = new String[args.length - 1];
System.arraycopy(args, 1, cmdArgs, 0, cmdArgs.length);
args = cmdArgs;
}
}
Command command = null;
try {
command = parse(cmd, args);
return command.execute(settings, env);
} catch (IOException ioe) {
terminal.printError(ioe);
return ExitStatus.IO_ERROR;
} catch (IllegalArgumentException ilae) {
terminal.printError(ilae);
return ExitStatus.USAGE;
} catch (Throwable t) {
terminal.printError(t);
if (command == null) {
return ExitStatus.USAGE;
}
return ExitStatus.CODE_ERROR;
}
}
public Command parse(String cmdName, String[] args) throws Exception {
CliToolConfig.Cmd cmd = config.cmd(cmdName);
return parse(cmd, args);
}
public Command parse(CliToolConfig.Cmd cmd, String[] args) throws Exception {
CommandLineParser parser = new DefaultParser();
CommandLine cli = parser.parse(CliToolConfig.OptionsSource.HELP.options(), args, true);
if (cli.hasOption("h")) {
return helpCmd(cmd);
}
cli = parser.parse(cmd.options(), args, cmd.isStopAtNonOption());
Terminal.Verbosity verbosity = Terminal.Verbosity.resolve(cli);
terminal.verbosity(verbosity);
return parse(cmd.name(), cli);
}
protected Command.Help helpCmd(CliToolConfig.Cmd cmd) {
return new Command.Help(cmd, terminal);
}
protected static Command.Exit exitCmd(ExitStatus status) {
return new Command.Exit(null, status, null);
}
protected static Command.Exit exitCmd(ExitStatus status, Terminal terminal, String msg, Object... args) {
return new Command.Exit(String.format(Locale.ROOT, msg, args), status, terminal);
}
protected abstract Command parse(String cmdName, CommandLine cli) throws Exception;
public static abstract class Command {
protected final Terminal terminal;
protected Command(Terminal terminal) {
this.terminal = terminal;
}
public abstract ExitStatus execute(Settings settings, Environment env) throws Exception;
public static class Help extends Command {
private final CliToolConfig.Cmd cmd;
private Help(CliToolConfig.Cmd cmd, Terminal terminal) {
super(terminal);
this.cmd = cmd;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
cmd.printUsage(terminal);
return ExitStatus.OK_AND_EXIT;
}
}
public static class Exit extends Command {
private final String msg;
private final ExitStatus status;
private Exit(String msg, ExitStatus status, Terminal terminal) {
super(terminal);
this.msg = msg;
this.status = status;
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
if (msg != null) {
if (status != ExitStatus.OK) {
terminal.printError(msg);
} else {
terminal.println(msg);
}
}
return status;
}
public ExitStatus status() {
return status;
}
}
}
}