Merge pull request #3067 from MitchAman/master

Make CLI options case sensitive for short names (#2844).
This commit is contained in:
Thomas Segismont
2019-10-09 10:45:31 +02:00
committed by GitHub
4 changed files with 83 additions and 26 deletions

View File

@@ -41,8 +41,10 @@ contain more details. Each option and argument are also added on the `CLI` objec
An {@link io.vertx.core.cli.Option} is a command line parameter identified by a _key_ present in the user command
line. Options must have at least a long name or a short name. Long name are generally used using a `--` prefix,
while short names are used with a single `-`. Options can get a description displayed in the usage (see below).
Options can receive 0, 1 or several values. An option receiving 0 values is a `flag`, and must be declared using
while short names are used with a single `-`. Names are case-sensitive; however, case-insensitive name matching
will be used during the <<query_interrogation_stage, Query / Interrogation Stage>> if no exact match is found.
Options can get a description displayed in the usage (see below). Options can receive 0, 1 or several values. An
option receiving 0 values is a `flag`, and must be declared using
{@link io.vertx.core.cli.Option#setFlag(boolean)}. By default, options receive a single value, however, you can
configure the option to receive several values using {@link io.vertx.core.cli.Option#setMultiValued(boolean)}:
@@ -152,6 +154,7 @@ This is useful if you want to check an argument or option is present even if the
You can check whether or not the
{@link io.vertx.core.cli.CommandLine} is valid using {@link io.vertx.core.cli.CommandLine#isValid()}.
[[query_interrogation_stage]]
=== Query / Interrogation Stage
Once parsed, you can retrieve the values of the options and arguments from the
@@ -163,8 +166,8 @@ method:
{@link examples.cli.CLIExamples#example8}
----
One of your option can have been marked as "help". If a user command line enabled a "help" option, the validation
won't failed, but give you the opportunity to check if the user asks for help:
One of your options can be marked as "help". If a user command line enabled a "help" option, the validation
won't fail, but you have the opportunity to check if the user asks for help:
[source,$lang]
----

View File

@@ -14,6 +14,7 @@ package io.vertx.core.cli.impl;
import io.vertx.core.cli.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -160,25 +161,25 @@ public class DefaultCLI implements CLI {
@Override
public Option getOption(String name) {
Objects.requireNonNull(name);
// The option by name look up is a three steps lookup:
// first check by long name
// then by short name
// finally by arg name
for (Option option : options) {
if (name.equalsIgnoreCase(option.getLongName())) {
return option;
}
}
List<Predicate<Option>> equalityChecks = Arrays.asList(
// The option by name look up is a three steps lookup:
// first check by long name
// then by short name
// finally by arg name
option -> name.equals(option.getLongName()),
option -> name.equals(option.getShortName()),
option -> name.equals(option.getArgName()),
// If there's no exact match, check again in the same order, this time ignoring case-sensitivity
option -> name.equalsIgnoreCase(option.getLongName()),
option -> name.equalsIgnoreCase(option.getShortName()),
option -> name.equalsIgnoreCase(option.getArgName())
);
for (Option option : options) {
if (name.equalsIgnoreCase(option.getShortName())) {
return option;
}
}
for (Option option : options) {
if (name.equalsIgnoreCase(option.getArgName())) {
return option;
for (Predicate<Option> equalityCheck : equalityChecks) {
for (Option option : options) {
if (equalityCheck.test(option)) {
return option;
}
}
}

View File

@@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -479,7 +480,7 @@ public class DefaultParser {
private boolean hasOptionWithLongName(String name) {
for (Option option : cli.getOptions()) {
if (name.equalsIgnoreCase(option.getLongName())) {
if (name.equals(option.getLongName())) {
return true;
}
}
@@ -488,7 +489,7 @@ public class DefaultParser {
private boolean hasOptionWithShortName(String name) {
for (Option option : cli.getOptions()) {
if (name.equalsIgnoreCase(option.getShortName())) {
if (name.equals(option.getShortName())) {
return true;
}
}
@@ -529,7 +530,7 @@ public class DefaultParser {
public Option getOption(String opt) {
opt = stripLeadingHyphens(opt);
for (Option option : cli.getOptions()) {
if (opt.equalsIgnoreCase(option.getShortName()) || opt.equalsIgnoreCase(option.getLongName())) {
if (opt.equals(option.getShortName()) || opt.equalsIgnoreCase(option.getLongName())) {
return option;
}
}
@@ -549,6 +550,7 @@ public class DefaultParser {
* @return the options matching the partial name specified, or an empty list if none matches
*/
public List<Option> getMatchingOptions(String opt) {
Objects.requireNonNull(opt);
opt = stripLeadingHyphens(opt);
List<Option> matching = new ArrayList<>();
@@ -558,11 +560,18 @@ public class DefaultParser {
// Exact match first
for (Option option : options) {
if (opt.equalsIgnoreCase(option.getLongName())) {
if (opt.equals(option.getLongName())) {
return Collections.singletonList(option);
}
}
// Case-insensitive match second
for (Option option : options) {
if (opt.equalsIgnoreCase(option.getLongName())) {
matching.add(option);
}
}
for (Option option : options) {
if (option.getLongName() != null && option.getLongName().startsWith(opt)) {
matching.add(option);

View File

@@ -802,4 +802,48 @@ public class DefaultParserTest {
}
@Test
public void testGetOptionValueWithCaseSensitivityConflict() {
final CLI cli = CLI.create("test")
.addOption(new Option().setShortName("a").setLongName("longname"))
.addOption(new Option().setShortName("A").setLongName("LONGNAME"));
String lowercaseValue = "someValue";
String uppercaseValue = "someOtherValue";
CommandLine commandLine = cli.parse(Arrays.asList("-a", lowercaseValue, "-A", uppercaseValue));
assertThat((String) commandLine.getOptionValue("a")).isEqualTo(lowercaseValue);
assertThat((String) commandLine.getOptionValue("A")).isEqualTo(uppercaseValue);
assertThat((String) commandLine.getOptionValue("longname")).isEqualTo(lowercaseValue);
assertThat((String) commandLine.getOptionValue("LONGNAME")).isEqualTo(uppercaseValue);
commandLine = cli.parse(Arrays.asList("--longname", lowercaseValue, "--LONGNAME", uppercaseValue));
assertThat((String) commandLine.getOptionValue("a")).isEqualTo(lowercaseValue);
assertThat((String) commandLine.getOptionValue("A")).isEqualTo(uppercaseValue);
assertThat((String) commandLine.getOptionValue("longname")).isEqualTo(lowercaseValue);
assertThat((String) commandLine.getOptionValue("LONGNAME")).isEqualTo(uppercaseValue);
}
@Test
public void testGetOptionValueWithoutCaseSensitivityConflict() {
// If there's no case-sensitivity conflict for a given short name, then using the opposite case when getting that
// option value should still work.
final CLI cli = CLI.create("test")
.addOption(new Option().setShortName("a").setLongName("lowercase"));
String value = "foo";
CommandLine commandLine = cli.parse(Arrays.asList("-a", value));
assertThat((String) commandLine.getOptionValue("a")).isEqualTo(value);
assertThat((String) commandLine.getOptionValue("A")).isEqualTo(value);
assertThat((String) commandLine.getOptionValue("lowercase")).isEqualTo(value);
assertThat((String) commandLine.getOptionValue("LOWERCASE")).isEqualTo(value);
commandLine = cli.parse(Arrays.asList("--lowercase", value));
assertThat((String) commandLine.getOptionValue("a")).isEqualTo(value);
assertThat((String) commandLine.getOptionValue("A")).isEqualTo(value);
assertThat((String) commandLine.getOptionValue("lowercase")).isEqualTo(value);
assertThat((String) commandLine.getOptionValue("LOWERCASE")).isEqualTo(value);
}
}