Skip to content

Commit 31e4679

Browse files
committed
Pass along -E arguments to subcommands
Having MultiCommand silently accept and ignore -E arguments could be confusing, so instead pass those arguments along to subcommands.
1 parent fcec60d commit 31e4679

File tree

2 files changed

+62
-24
lines changed

2 files changed

+62
-24
lines changed

libs/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121

2222
import joptsimple.NonOptionArgumentSpec;
2323
import joptsimple.OptionSet;
24+
import joptsimple.OptionSpec;
25+
import joptsimple.util.KeyValuePair;
2426
import org.elasticsearch.core.internal.io.IOUtils;
2527

2628
import java.io.IOException;
27-
import java.util.Arrays;
29+
import java.util.ArrayList;
2830
import java.util.LinkedHashMap;
31+
import java.util.List;
2932
import java.util.Map;
3033

3134
/**
@@ -36,6 +39,7 @@ public class MultiCommand extends Command {
3639
protected final Map<String, Command> subcommands = new LinkedHashMap<>();
3740

3841
private final NonOptionArgumentSpec<String> arguments = parser.nonOptions("command");
42+
private final OptionSpec<KeyValuePair> settingOption;
3943

4044
/**
4145
* Construct the multi-command with the specified command description and runnable to execute before main is invoked.
@@ -45,9 +49,7 @@ public class MultiCommand extends Command {
4549
*/
4650
public MultiCommand(final String description, final Runnable beforeMain) {
4751
super(description, beforeMain);
48-
// Accepting -E here does not prevent subcommands receiving -E arguments, since we also set `posixlyCorrect` below.
49-
// This stops option parsing when the first non-option is encountered.
50-
parser.accepts("E", "Unused. Pass to a subcommand instead").withRequiredArg();
52+
this.settingOption = parser.accepts("E", "Configure a setting").withRequiredArg().ofType(KeyValuePair.class);
5153
parser.posixlyCorrect(true);
5254
}
5355

@@ -69,15 +71,24 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception {
6971
if (subcommands.isEmpty()) {
7072
throw new IllegalStateException("No subcommands configured");
7173
}
72-
String[] args = arguments.values(options).toArray(new String[0]);
73-
if (args.length == 0) {
74+
75+
// .values(...) returns an unmodifiable list
76+
final List<String> args = new ArrayList<>(arguments.values(options));
77+
if (args.isEmpty()) {
7478
throw new UserException(ExitCodes.USAGE, "Missing command");
7579
}
76-
Command subcommand = subcommands.get(args[0]);
80+
81+
String subcommandName = args.remove(0);
82+
Command subcommand = subcommands.get(subcommandName);
7783
if (subcommand == null) {
78-
throw new UserException(ExitCodes.USAGE, "Unknown command [" + args[0] + "]");
84+
throw new UserException(ExitCodes.USAGE, "Unknown command [" + subcommandName + "]");
7985
}
80-
subcommand.mainWithoutErrorHandling(Arrays.copyOfRange(args, 1, args.length), terminal);
86+
87+
for (final KeyValuePair pair : this.settingOption.values(options)) {
88+
args.add("-E" + pair);
89+
}
90+
91+
subcommand.mainWithoutErrorHandling(args.toArray(new String[0]), terminal);
8192
}
8293

8394
@Override

server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,25 @@
1919

2020
package org.elasticsearch.cli;
2121

22+
import joptsimple.ArgumentAcceptingOptionSpec;
2223
import joptsimple.OptionSet;
24+
import joptsimple.util.KeyValuePair;
2325
import org.junit.Before;
2426

2527
import java.io.IOException;
28+
import java.util.List;
2629
import java.util.concurrent.atomic.AtomicBoolean;
2730

31+
import static org.hamcrest.Matchers.containsString;
32+
2833
public class MultiCommandTests extends CommandTestCase {
2934

3035
static class DummyMultiCommand extends MultiCommand {
3136

3237
final AtomicBoolean closed = new AtomicBoolean();
3338

3439
DummyMultiCommand() {
35-
super("A dummy multi command", () -> {
36-
});
40+
super("A dummy multi command", () -> {});
3741
}
3842

3943
@Override
@@ -75,7 +79,23 @@ public void close() throws IOException {
7579
}
7680
}
7781

78-
DummyMultiCommand multiCommand;
82+
static class DummySettingsSubCommand extends DummySubCommand {
83+
private final ArgumentAcceptingOptionSpec<KeyValuePair> settingOption;
84+
85+
DummySettingsSubCommand() {
86+
super();
87+
this.settingOption = parser.accepts("E", "Configure a setting").withRequiredArg().ofType(KeyValuePair.class);
88+
}
89+
90+
@Override
91+
protected void execute(Terminal terminal, OptionSet options) throws Exception {
92+
final List<KeyValuePair> values = this.settingOption.values(options);
93+
terminal.println("Settings: " + values);
94+
super.execute(terminal, options);
95+
}
96+
}
97+
98+
private DummyMultiCommand multiCommand;
7999

80100
@Before
81101
public void setupCommand() {
@@ -87,27 +107,21 @@ protected Command newCommand() {
87107
return multiCommand;
88108
}
89109

90-
public void testNoCommandsConfigured() throws Exception {
91-
IllegalStateException e = expectThrows(IllegalStateException.class, () -> {
92-
execute();
93-
});
110+
public void testNoCommandsConfigured() {
111+
IllegalStateException e = expectThrows(IllegalStateException.class, this::execute);
94112
assertEquals("No subcommands configured", e.getMessage());
95113
}
96114

97-
public void testUnknownCommand() throws Exception {
115+
public void testUnknownCommand() {
98116
multiCommand.subcommands.put("something", new DummySubCommand());
99-
UserException e = expectThrows(UserException.class, () -> {
100-
execute("somethingelse");
101-
});
117+
UserException e = expectThrows(UserException.class, () -> execute("somethingelse"));
102118
assertEquals(ExitCodes.USAGE, e.exitCode);
103119
assertEquals("Unknown command [somethingelse]", e.getMessage());
104120
}
105121

106-
public void testMissingCommand() throws Exception {
122+
public void testMissingCommand() {
107123
multiCommand.subcommands.put("command1", new DummySubCommand());
108-
UserException e = expectThrows(UserException.class, () -> {
109-
execute();
110-
});
124+
UserException e = expectThrows(UserException.class, this::execute);
111125
assertEquals(ExitCodes.USAGE, e.exitCode);
112126
assertEquals("Missing command", e.getMessage());
113127
}
@@ -121,6 +135,19 @@ public void testHelp() throws Exception {
121135
assertTrue(output, output.contains("command2"));
122136
}
123137

138+
/**
139+
* Check that if -E arguments are passed to the main command, then they are accepted
140+
* and passed on to the subcommand.
141+
*/
142+
public void testSettingsOnMainCommand() throws Exception {
143+
multiCommand.subcommands.put("command1", new DummySettingsSubCommand());
144+
execute("-Esetting1=value1", "-Esetting2=value2", "command1", "otherArg");
145+
146+
String output = terminal.getOutput();
147+
assertThat(output, containsString("Settings: [setting1=value1, setting2=value2]"));
148+
assertThat(output, containsString("Arguments: [otherArg]"));
149+
}
150+
124151
public void testSubcommandHelp() throws Exception {
125152
multiCommand.subcommands.put("command1", new DummySubCommand());
126153
multiCommand.subcommands.put("command2", new DummySubCommand());

0 commit comments

Comments
 (0)