Added a cli infrastructure
CliTool is a base class for command-line interface tools (such as the plugin manager and potentially others). It supports the following: - single or multi command tool - help printing infrastructure (based on help files) - consistent mechanism of parsing arguments (based on commons-cli lib) - separation of argument parsing and command execution (for easier unit testing) - terminal abstraction (will use System.console() when available)
This commit is contained in:
parent
2d31349ab0
commit
5ccc7beaf4
15
pom.xml
15
pom.xml
|
@ -263,6 +263,13 @@
|
|||
<version>3.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- END: dependencies that are shaded -->
|
||||
|
||||
<dependency>
|
||||
|
@ -667,6 +674,7 @@
|
|||
<include>com.ning:compress-lzf</include>
|
||||
<include>com.github.spullara.mustache.java:compiler</include>
|
||||
<include>com.tdunning:t-digest</include>
|
||||
<include>commons-cli:commons-cli</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<relocations>
|
||||
|
@ -706,6 +714,10 @@
|
|||
<pattern>com.tdunning.math.stats</pattern>
|
||||
<shadedPattern>org.elasticsearch.common.stats</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.apache.commons.cli</pattern>
|
||||
<shadedPattern>org.elasticsearch.common.cli.commons</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<filters>
|
||||
<filter>
|
||||
|
@ -1153,7 +1165,10 @@
|
|||
<exclude>jsr166e/**</exclude>
|
||||
<!-- start excludes for valid system-out -->
|
||||
<exclude>org/elasticsearch/common/logging/log4j/ConsoleAppender*</exclude>
|
||||
<exclude>org/elasticsearch/common/http/client/HttpDownloadHelper*</exclude>
|
||||
<exclude>org/elasticsearch/common/cli/Terminal*</exclude>
|
||||
<exclude>org/elasticsearch/plugins/PluginManager.class</exclude>
|
||||
<exclude>org/elasticsearch/common/http/client/HttpDownloadHelper.class</exclude>
|
||||
<exclude>org/elasticsearch/bootstrap/Bootstrap.class</exclude>
|
||||
<exclude>org/elasticsearch/Version.class</exclude>
|
||||
<exclude>org/elasticsearch/common/lucene/search/Queries$QueryWrapperFilterFactory.class</exclude>
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* 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.GnuParser;
|
||||
import org.elasticsearch.ElasticsearchIllegalArgumentException;
|
||||
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.ImmutableSettings.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),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
Tuple<Settings, Environment> tuple = InternalSettingsPreparer.prepareSettings(EMPTY_SETTINGS, true);
|
||||
settings = tuple.v1();
|
||||
env = tuple.v2();
|
||||
}
|
||||
|
||||
public final int 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.status;
|
||||
}
|
||||
|
||||
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.status;
|
||||
}
|
||||
|
||||
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.status;
|
||||
}
|
||||
|
||||
// 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).status;
|
||||
|
||||
} catch (IOException ioe) {
|
||||
terminal.printError(ioe.getMessage());
|
||||
return ExitStatus.IO_ERROR.status;
|
||||
} catch (IllegalArgumentException | ElasticsearchIllegalArgumentException ilae) {
|
||||
terminal.printError(ilae.getMessage());
|
||||
return ExitStatus.USAGE.status;
|
||||
} catch (Throwable t) {
|
||||
terminal.printError(t.getMessage());
|
||||
if (command == null) {
|
||||
return ExitStatus.USAGE.status;
|
||||
}
|
||||
return ExitStatus.CODE_ERROR.status;
|
||||
}
|
||||
}
|
||||
|
||||
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 GnuParser();
|
||||
CommandLine cli = parser.parse(CliToolConfig.OptionsSource.HELP.options(), args, true);
|
||||
if (cli.hasOption("h")) {
|
||||
return helpCmd(cmd);
|
||||
}
|
||||
cli = parser.parse(cmd.options(), args);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* 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.collect.ImmutableMap;
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.OptionGroup;
|
||||
import org.apache.commons.cli.Options;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CliToolConfig {
|
||||
|
||||
public static Builder config(String name, Class<? extends CliTool> toolType) {
|
||||
return new Builder(name, toolType);
|
||||
}
|
||||
|
||||
private final Class<? extends CliTool> toolType;
|
||||
private final String name;
|
||||
private final ImmutableMap<String, Cmd> cmds;
|
||||
|
||||
private static final HelpPrinter helpPrinter = new HelpPrinter();
|
||||
|
||||
private CliToolConfig(String name, Class<? extends CliTool> toolType, Cmd[] cmds) {
|
||||
this.name = name;
|
||||
this.toolType = toolType;
|
||||
ImmutableMap.Builder<String, Cmd> cmdsBuilder = ImmutableMap.builder();
|
||||
for (int i = 0; i < cmds.length; i++) {
|
||||
cmdsBuilder.put(cmds[i].name, cmds[i]);
|
||||
}
|
||||
this.cmds = cmdsBuilder.build();
|
||||
}
|
||||
|
||||
public boolean isSingle() {
|
||||
return cmds.size() == 1;
|
||||
}
|
||||
|
||||
public Cmd single() {
|
||||
assert isSingle() : "Requesting single command on a multi-command tool";
|
||||
return cmds.values().iterator().next();
|
||||
}
|
||||
|
||||
public Class<? extends CliTool> toolType() {
|
||||
return toolType;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Collection<Cmd> cmds() {
|
||||
return cmds.values();
|
||||
}
|
||||
|
||||
public Cmd cmd(String name) {
|
||||
return cmds.get(name);
|
||||
}
|
||||
|
||||
public void printUsage(Terminal terminal) {
|
||||
helpPrinter.print(this, terminal);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
public static Cmd.Builder cmd(String name, Class<? extends CliTool.Command> cmdType) {
|
||||
return new Cmd.Builder(name, cmdType);
|
||||
}
|
||||
|
||||
public static OptionBuilder option(String shortName, String longName) {
|
||||
return new OptionBuilder(shortName, longName);
|
||||
}
|
||||
|
||||
public static OptionGroupBuilder optionGroup(boolean required) {
|
||||
return new OptionGroupBuilder(required);
|
||||
}
|
||||
|
||||
private final Class<? extends CliTool> toolType;
|
||||
private final String name;
|
||||
private Cmd[] cmds;
|
||||
|
||||
private Builder(String name, Class<? extends CliTool> toolType) {
|
||||
this.name = name;
|
||||
this.toolType = toolType;
|
||||
}
|
||||
|
||||
public Builder cmds(Cmd.Builder... cmds) {
|
||||
this.cmds = new Cmd[cmds.length];
|
||||
for (int i = 0; i < cmds.length; i++) {
|
||||
this.cmds[i] = cmds[i].build();
|
||||
this.cmds[i].toolName = name;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cmds(Cmd... cmds) {
|
||||
for (int i = 0; i < cmds.length; i++) {
|
||||
cmds[i].toolName = name;
|
||||
}
|
||||
this.cmds = cmds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CliToolConfig build() {
|
||||
return new CliToolConfig(name, toolType, cmds);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Cmd {
|
||||
|
||||
private String toolName;
|
||||
private final String name;
|
||||
private final Class<? extends CliTool.Command> cmdType;
|
||||
private final Options options;
|
||||
|
||||
private Cmd(String name, Class<? extends CliTool.Command> cmdType, Options options) {
|
||||
this.name = name;
|
||||
this.cmdType = cmdType;
|
||||
this.options = options;
|
||||
OptionsSource.VERBOSITY.populate(options);
|
||||
}
|
||||
|
||||
public Class<? extends CliTool.Command> cmdType() {
|
||||
return cmdType;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Options options() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void printUsage(Terminal terminal) {
|
||||
helpPrinter.print(toolName, this, terminal);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final String name;
|
||||
private final Class<? extends CliTool.Command> cmdType;
|
||||
private Options options = new Options();
|
||||
|
||||
private Builder(String name, Class<? extends CliTool.Command> cmdType) {
|
||||
this.name = name;
|
||||
this.cmdType = cmdType;
|
||||
}
|
||||
|
||||
public Builder options(OptionBuilder... optionBuilder) {
|
||||
for (int i = 0; i < optionBuilder.length; i++) {
|
||||
options.addOption(optionBuilder[i].build());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder optionGroups(OptionGroupBuilder... optionGroupBuilders) {
|
||||
for (OptionGroupBuilder builder : optionGroupBuilders) {
|
||||
options.addOptionGroup(builder.build());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Cmd build() {
|
||||
return new Cmd(name, cmdType, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class OptionBuilder {
|
||||
|
||||
private final Option option;
|
||||
|
||||
private OptionBuilder(String shortName, String longName) {
|
||||
option = new Option(shortName, "");
|
||||
option.setLongOpt(longName);
|
||||
option.setArgName(longName);
|
||||
}
|
||||
|
||||
public OptionBuilder required(boolean required) {
|
||||
option.setRequired(required);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionBuilder hasArg(boolean optional) {
|
||||
option.setOptionalArg(optional);
|
||||
option.setArgs(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Option build() {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OptionGroupBuilder {
|
||||
|
||||
private OptionGroup group;
|
||||
|
||||
private OptionGroupBuilder(boolean required) {
|
||||
group = new OptionGroup();
|
||||
group.setRequired(required);
|
||||
}
|
||||
|
||||
public OptionGroupBuilder options(OptionBuilder... optionBuilders) {
|
||||
for (OptionBuilder builder : optionBuilders) {
|
||||
group.addOption(builder.build());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionGroup build() {
|
||||
return group;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static abstract class OptionsSource {
|
||||
|
||||
static final OptionsSource HELP = new OptionsSource() {
|
||||
|
||||
@Override
|
||||
void populate(Options options) {
|
||||
options.addOption(new OptionBuilder("h", "help").required(false).build());
|
||||
}
|
||||
};
|
||||
|
||||
static final OptionsSource VERBOSITY = new OptionsSource() {
|
||||
@Override
|
||||
void populate(Options options) {
|
||||
OptionGroup verbosityGroup = new OptionGroup();
|
||||
verbosityGroup.setRequired(false);
|
||||
verbosityGroup.addOption(new OptionBuilder("s", "silent").required(false).build());
|
||||
verbosityGroup.addOption(new OptionBuilder("v", "verbose").required(false).build());
|
||||
options.addOptionGroup(verbosityGroup);
|
||||
}
|
||||
};
|
||||
|
||||
private Options options;
|
||||
|
||||
Options options() {
|
||||
if (options == null) {
|
||||
options = new Options();
|
||||
populate(options);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
abstract void populate(Options options);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.common.util.Callback;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class HelpPrinter {
|
||||
|
||||
private static final String HELP_FILE_EXT = ".help";
|
||||
|
||||
public void print(CliToolConfig config, Terminal terminal) {
|
||||
print(config.toolType(), config.name(), terminal);
|
||||
}
|
||||
|
||||
public void print(String toolName, CliToolConfig.Cmd cmd, Terminal terminal) {
|
||||
print(cmd.cmdType(), toolName + "-" + cmd.name(), terminal);
|
||||
}
|
||||
|
||||
private static void print(Class clazz, String name, final Terminal terminal) {
|
||||
terminal.println(Terminal.Verbosity.SILENT);
|
||||
try (InputStream input = clazz.getResourceAsStream(name + HELP_FILE_EXT)) {
|
||||
Streams.readAllLines(input, new Callback<String>() {
|
||||
@Override
|
||||
public void handle(String line) {
|
||||
terminal.println(Terminal.Verbosity.SILENT, line);
|
||||
}
|
||||
});
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace(terminal.writer());
|
||||
}
|
||||
terminal.println();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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 org.apache.commons.cli.CommandLine;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class Terminal {
|
||||
|
||||
public static final Terminal DEFAULT = ConsoleTerminal.supported() ? new ConsoleTerminal() : new SystemTerminal();
|
||||
|
||||
public static enum Verbosity {
|
||||
SILENT(0), NORMAL(1), VERBOSE(2);
|
||||
|
||||
private final int level;
|
||||
|
||||
private Verbosity(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public boolean enabled(Verbosity verbosity) {
|
||||
return level >= verbosity.level;
|
||||
}
|
||||
|
||||
public static Verbosity resolve(CommandLine cli) {
|
||||
if (cli.hasOption("s")) {
|
||||
return SILENT;
|
||||
}
|
||||
if (cli.hasOption("v")) {
|
||||
return VERBOSE;
|
||||
}
|
||||
return NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
private Verbosity verbosity = Verbosity.NORMAL;
|
||||
|
||||
|
||||
public Terminal() {
|
||||
this(Verbosity.NORMAL);
|
||||
}
|
||||
|
||||
public Terminal(Verbosity verbosity) {
|
||||
this.verbosity = verbosity;
|
||||
}
|
||||
|
||||
public void verbosity(Verbosity verbosity) {
|
||||
this.verbosity = verbosity;
|
||||
}
|
||||
|
||||
public Verbosity verbosity() {
|
||||
return verbosity;
|
||||
}
|
||||
|
||||
public abstract String readText(String text, Object... args);
|
||||
|
||||
public abstract char[] readSecret(String text, Object... args);
|
||||
|
||||
public void println() {
|
||||
println(Verbosity.NORMAL);
|
||||
}
|
||||
|
||||
public void println(String msg, Object... args) {
|
||||
println(Verbosity.NORMAL, msg, args);
|
||||
}
|
||||
|
||||
public void print(String msg, Object... args) {
|
||||
print(Verbosity.NORMAL, msg, args);
|
||||
}
|
||||
|
||||
public void println(Verbosity verbosity) {
|
||||
println(verbosity, "");
|
||||
}
|
||||
|
||||
public void println(Verbosity verbosity, String msg, Object... args) {
|
||||
print(verbosity, msg + System.lineSeparator(), args);
|
||||
}
|
||||
|
||||
public void print(Verbosity verbosity, String msg, Object... args) {
|
||||
if (this.verbosity.enabled(verbosity)) {
|
||||
doPrint(msg, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void printError(String msg, Object... args) {
|
||||
println(Verbosity.SILENT, "ERROR: " + msg, args);
|
||||
}
|
||||
|
||||
protected abstract void doPrint(String msg, Object... args);
|
||||
|
||||
public abstract PrintWriter writer();
|
||||
|
||||
private static class ConsoleTerminal extends Terminal {
|
||||
|
||||
final Console console = System.console();
|
||||
|
||||
static boolean supported() {
|
||||
return System.console() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doPrint(String msg, Object... args) {
|
||||
console.printf(msg, args);
|
||||
console.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
return console.readLine(text, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return console.readPassword(text, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter writer() {
|
||||
return console.writer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SystemTerminal extends Terminal {
|
||||
|
||||
private final PrintWriter printWriter = new PrintWriter(System.out);
|
||||
|
||||
@Override
|
||||
public void doPrint(String msg, Object... args) {
|
||||
System.out.print(String.format(Locale.ROOT, msg, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
print(text, args);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
try {
|
||||
return reader.readLine();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return readText(text, args).toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter writer() {
|
||||
return printWriter;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -130,7 +130,7 @@ public class HttpDownloadHelper {
|
|||
public static class VerboseProgress implements DownloadProgress {
|
||||
private int dots = 0;
|
||||
// CheckStyle:VisibilityModifier OFF - bc
|
||||
PrintStream out;
|
||||
PrintWriter writer;
|
||||
// CheckStyle:VisibilityModifier ON
|
||||
|
||||
/**
|
||||
|
@ -139,14 +139,23 @@ public class HttpDownloadHelper {
|
|||
* @param out the output stream.
|
||||
*/
|
||||
public VerboseProgress(PrintStream out) {
|
||||
this.out = out;
|
||||
this.writer = new PrintWriter(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a verbose progress reporter.
|
||||
*
|
||||
* @param writer the output stream.
|
||||
*/
|
||||
public VerboseProgress(PrintWriter writer) {
|
||||
this.writer = this.writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* begin a download
|
||||
*/
|
||||
public void beginDownload() {
|
||||
out.print("Downloading ");
|
||||
writer.print("Downloading ");
|
||||
dots = 0;
|
||||
}
|
||||
|
||||
|
@ -154,9 +163,9 @@ public class HttpDownloadHelper {
|
|||
* tick handler
|
||||
*/
|
||||
public void onTick() {
|
||||
out.print(".");
|
||||
writer.print(".");
|
||||
if (dots++ > 50) {
|
||||
out.flush();
|
||||
writer.flush();
|
||||
dots = 0;
|
||||
}
|
||||
}
|
||||
|
@ -165,8 +174,8 @@ public class HttpDownloadHelper {
|
|||
* end a download
|
||||
*/
|
||||
public void endDownload() {
|
||||
out.println("DONE");
|
||||
out.flush();
|
||||
writer.println("DONE");
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,10 +20,13 @@
|
|||
package org.elasticsearch.common.io;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.elasticsearch.common.Preconditions;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.util.Callback;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Simple utility methods for file and stream copying.
|
||||
|
@ -297,4 +300,24 @@ public abstract class Streams {
|
|||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
public static List<String> readAllLines(InputStream input) throws IOException {
|
||||
final List<String> lines = Lists.newArrayList();
|
||||
readAllLines(input, new Callback<String>() {
|
||||
@Override
|
||||
public void handle(String line) {
|
||||
lines.add(line);
|
||||
}
|
||||
});
|
||||
return lines;
|
||||
}
|
||||
|
||||
public static void readAllLines(InputStream input, Callback<String> callback) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, Charsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
callback.handle(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface Callback<T> {
|
||||
|
||||
void handle(T t);
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CliToolTestCase extends ElasticsearchTestCase {
|
||||
|
||||
protected static String[] args(String command) {
|
||||
if (!Strings.hasLength(command)) {
|
||||
return Strings.EMPTY_ARRAY;
|
||||
}
|
||||
return command.split("\\s+");
|
||||
}
|
||||
|
||||
public static class TerminalMock extends Terminal {
|
||||
|
||||
private static final PrintWriter DEV_NULL = new PrintWriter(new DevNullWriter());
|
||||
|
||||
public TerminalMock() {
|
||||
super(Verbosity.NORMAL);
|
||||
}
|
||||
|
||||
public TerminalMock(Verbosity verbosity) {
|
||||
super(verbosity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPrint(String msg, Object... args) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return new char[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String msg, Object... args) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter writer() {
|
||||
return DEV_NULL;
|
||||
}
|
||||
|
||||
private static class DevNullWriter extends Writer {
|
||||
|
||||
@Override
|
||||
public void write(char[] cbuf, int off, int len) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
* 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.collect.ImmutableMap;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CliToolTests extends CliToolTestCase {
|
||||
|
||||
@Test
|
||||
public void testOK() throws Exception {
|
||||
Terminal terminal = new TerminalMock();
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) {
|
||||
executed.set(true);
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute();
|
||||
assertExecuted(status, CliTool.ExitStatus.OK, executed, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsageError() throws Exception {
|
||||
Terminal terminal = new TerminalMock();
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) {
|
||||
executed.set(true);
|
||||
return CliTool.ExitStatus.USAGE;
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute();
|
||||
assertExecuted(status, CliTool.ExitStatus.USAGE, executed, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIOError() throws Exception {
|
||||
Terminal terminal = new TerminalMock();
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed.set(true);
|
||||
throw new IOException("io error");
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute();
|
||||
assertExecuted(status, CliTool.ExitStatus.IO_ERROR, executed, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCodeError() throws Exception {
|
||||
Terminal terminal = new TerminalMock();
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed.set(true);
|
||||
throw new Exception("random error");
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute();
|
||||
assertExecuted(status, CliTool.ExitStatus.CODE_ERROR, executed, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiCommand() {
|
||||
Terminal terminal = new TerminalMock();
|
||||
int count = randomIntBetween(2, 7);
|
||||
final AtomicReference<Boolean>[] executed = new AtomicReference[count];
|
||||
for (int i = 0; i < executed.length; i++) {
|
||||
executed[i] = new AtomicReference<>(false);
|
||||
}
|
||||
NamedCommand[] cmds = new NamedCommand[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int index = i;
|
||||
cmds[i] = new NamedCommand("cmd" + index, terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed[index].set(true);
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
}
|
||||
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
|
||||
int cmdIndex = randomIntBetween(0, count-1);
|
||||
int status = tool.execute("cmd" + cmdIndex);
|
||||
assertThat(status, is(CliTool.ExitStatus.OK.status()));
|
||||
for (int i = 0; i < executed.length; i++) {
|
||||
assertThat(executed[i].get(), is(i == cmdIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiCommand_UnknownCommand() {
|
||||
Terminal terminal = new TerminalMock();
|
||||
int count = randomIntBetween(2, 7);
|
||||
final AtomicReference<Boolean>[] executed = new AtomicReference[count];
|
||||
for (int i = 0; i < executed.length; i++) {
|
||||
executed[i] = new AtomicReference<>(false);
|
||||
}
|
||||
NamedCommand[] cmds = new NamedCommand[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int index = i;
|
||||
cmds[i] = new NamedCommand("cmd" + index, terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed[index].set(true);
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
}
|
||||
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
|
||||
int status = tool.execute("cmd" + count); // "cmd" + count doesn't exist
|
||||
assertThat(status, is(CliTool.ExitStatus.USAGE.status()));
|
||||
for (int i = 0; i < executed.length; i++) {
|
||||
assertThat(executed[i].get(), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleCommand_ToolHelp() throws Exception {
|
||||
final AtomicInteger writeCounter = new AtomicInteger(0);
|
||||
Terminal terminal = new TerminalMock() {
|
||||
@Override
|
||||
public void doPrint(String msg, Object... args) {
|
||||
int count = writeCounter.incrementAndGet();
|
||||
switch (count) {
|
||||
case 1:
|
||||
assertThat(msg, equalTo("\n"));
|
||||
break;
|
||||
case 2:
|
||||
assertThat(msg, equalTo("cmd1 help\n"));
|
||||
break;
|
||||
case 3:
|
||||
assertThat(msg, equalTo("\n"));
|
||||
break;
|
||||
default:
|
||||
fail("written more than expected");
|
||||
}
|
||||
}
|
||||
};
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd1", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed.set(true);
|
||||
throw new IOException("io error");
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute(args("-h"));
|
||||
assertExecuted(status, CliTool.ExitStatus.OK, writeCounter, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiCommand_ToolHelp() {
|
||||
final AtomicInteger writeCounter = new AtomicInteger(0);
|
||||
Terminal terminal = new TerminalMock() {
|
||||
@Override
|
||||
public void doPrint(String msg, Object... args) {
|
||||
int count = writeCounter.incrementAndGet();
|
||||
switch (count) {
|
||||
case 1:
|
||||
assertThat(msg, equalTo("\n"));
|
||||
break;
|
||||
case 2:
|
||||
assertThat(msg, equalTo("tool help\n"));
|
||||
break;
|
||||
case 3:
|
||||
assertThat(msg, equalTo("\n"));
|
||||
break;
|
||||
default:
|
||||
fail("written more than expected");
|
||||
}
|
||||
}
|
||||
};
|
||||
NamedCommand[] cmds = new NamedCommand[2];
|
||||
cmds[0] = new NamedCommand("cmd0", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
cmds[1] = new NamedCommand("cmd1", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
|
||||
int status = tool.execute(args("-h"));
|
||||
assertExecuted(status, CliTool.ExitStatus.OK, writeCounter, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiCommand_CmdHelp() {
|
||||
final AtomicInteger writeCounter = new AtomicInteger(0);
|
||||
Terminal terminal = new TerminalMock() {
|
||||
@Override
|
||||
public void doPrint(String msg, Object... args) {
|
||||
int count = writeCounter.incrementAndGet();
|
||||
switch (count) {
|
||||
case 1:
|
||||
assertThat(msg, equalTo("\n"));
|
||||
break;
|
||||
case 2:
|
||||
assertThat(msg, equalTo("cmd1 help\n"));
|
||||
break;
|
||||
case 3:
|
||||
assertThat(msg, equalTo("\n"));
|
||||
break;
|
||||
default:
|
||||
fail("written more than expected");
|
||||
}
|
||||
}
|
||||
};
|
||||
NamedCommand[] cmds = new NamedCommand[2];
|
||||
cmds[0] = new NamedCommand("cmd0", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
cmds[1] = new NamedCommand("cmd1", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
|
||||
int status = tool.execute(args("cmd1 -h"));
|
||||
assertExecuted(status, CliTool.ExitStatus.OK, writeCounter, 3);
|
||||
}
|
||||
|
||||
private static void assertExecuted(int actualStatus, CliTool.ExitStatus expectedStatus, AtomicReference<Boolean> actualExecuted, boolean expectedExecuted) {
|
||||
assertThat(actualExecuted.get(), is(expectedExecuted));
|
||||
assertThat(actualStatus, is(expectedStatus.status()));
|
||||
}
|
||||
|
||||
private static void assertExecuted(int actualStatus, CliTool.ExitStatus expectedStatus, AtomicInteger actualExecuted, int expectedExecuted) {
|
||||
assertThat(actualExecuted.get(), is(expectedExecuted));
|
||||
assertThat(actualStatus, is(expectedStatus.status()));
|
||||
}
|
||||
|
||||
private static class SingleCmdTool extends CliTool {
|
||||
|
||||
private final Command command;
|
||||
|
||||
private SingleCmdTool(String name, Terminal terminal, NamedCommand command) {
|
||||
super(CliToolConfig.config(name, SingleCmdTool.class)
|
||||
.cmds(cmd(command.name, command.getClass()))
|
||||
.build(), terminal);
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Command parse(String cmdName, CommandLine cli) throws Exception {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MultiCmdTool extends CliTool {
|
||||
|
||||
private final Map<String, Command> commands;
|
||||
|
||||
private MultiCmdTool(String name, Terminal terminal, NamedCommand... commands) {
|
||||
super(CliToolConfig.config(name, MultiCmdTool.class)
|
||||
.cmds(cmds(commands))
|
||||
.build(), terminal);
|
||||
ImmutableMap.Builder<String, Command> commandByName = ImmutableMap.builder();
|
||||
for (int i = 0; i < commands.length; i++) {
|
||||
commandByName.put(commands[i].name, commands[i]);
|
||||
}
|
||||
this.commands = commandByName.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Command parse(String cmdName, CommandLine cli) throws Exception {
|
||||
return commands.get(cmdName);
|
||||
}
|
||||
|
||||
private static CliToolConfig.Cmd[] cmds(NamedCommand... commands) {
|
||||
CliToolConfig.Cmd[] cmds = new CliToolConfig.Cmd[commands.length];
|
||||
for (int i = 0; i < commands.length; i++) {
|
||||
cmds[i] = cmd(commands[i].name, commands[i].getClass()).build();
|
||||
}
|
||||
return cmds;
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class NamedCommand extends CliTool.Command {
|
||||
|
||||
private final String name;
|
||||
|
||||
private NamedCommand(String name, Terminal terminal) {
|
||||
super(terminal);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 org.junit.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TerminalTests extends CliToolTestCase {
|
||||
|
||||
@Test
|
||||
public void testVerbosity() throws Exception {
|
||||
final AtomicReference<Boolean> printed = new AtomicReference<>(false);
|
||||
|
||||
Terminal terminal = new TerminalMock(Terminal.Verbosity.SILENT) {
|
||||
@Override
|
||||
protected void doPrint(String msg, Object... args) {
|
||||
printed.set(true);
|
||||
assertThat(msg, equalTo("text"));
|
||||
}
|
||||
};
|
||||
terminal.print(Terminal.Verbosity.SILENT, "text");
|
||||
assertPrinted(printed);
|
||||
terminal.print(Terminal.Verbosity.NORMAL, "text");
|
||||
assertNotPrinted(printed);
|
||||
terminal.print(Terminal.Verbosity.VERBOSE, "text");
|
||||
assertNotPrinted(printed);
|
||||
|
||||
terminal = new TerminalMock(Terminal.Verbosity.NORMAL) {
|
||||
@Override
|
||||
protected void doPrint(String msg, Object... args) {
|
||||
printed.set(true);
|
||||
assertThat(msg, equalTo("text"));
|
||||
}
|
||||
};
|
||||
terminal.print(Terminal.Verbosity.SILENT, "text");
|
||||
assertPrinted(printed);
|
||||
terminal.print(Terminal.Verbosity.NORMAL, "text");
|
||||
assertPrinted(printed);
|
||||
terminal.print(Terminal.Verbosity.VERBOSE, "text");
|
||||
assertNotPrinted(printed);
|
||||
|
||||
terminal = new TerminalMock(Terminal.Verbosity.VERBOSE) {
|
||||
@Override
|
||||
protected void doPrint(String msg, Object... args) {
|
||||
printed.set(true);
|
||||
assertThat(msg, equalTo("text"));
|
||||
}
|
||||
};
|
||||
terminal.print(Terminal.Verbosity.SILENT, "text");
|
||||
assertPrinted(printed);
|
||||
terminal.print(Terminal.Verbosity.NORMAL, "text");
|
||||
assertPrinted(printed);
|
||||
terminal.print(Terminal.Verbosity.VERBOSE, "text");
|
||||
assertPrinted(printed);
|
||||
}
|
||||
|
||||
private static void assertPrinted(AtomicReference<Boolean> actual) {
|
||||
assertThat(actual.get(), is(true));
|
||||
actual.set(false); // resetting
|
||||
}
|
||||
|
||||
private static void assertNotPrinted(AtomicReference<Boolean> actual) {
|
||||
assertThat(actual.get(), is(false));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
cmd1 help
|
|
@ -0,0 +1 @@
|
|||
tool help
|
Loading…
Reference in New Issue