Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.springframework.shell.core.command;

import org.jspecify.annotations.Nullable;
import org.springframework.util.StringUtils;

/**
* Record representing the definition as well as the runtime information about a command
Expand All @@ -24,10 +25,16 @@
* @author Janne Valkealahti
* @author Piotr Olaszewski
* @author Mahmoud Ben Hassine
* @author David Pilar
*/
public record CommandOption(char shortName, @Nullable String longName, @Nullable String description,
@Nullable Boolean required, @Nullable String defaultValue, @Nullable String value, Class<?> type) {

public boolean isOptionEqual(String optionName) {
return StringUtils.hasLength(longName) && optionName.equals("--" + longName)
|| shortName != ' ' && optionName.equals("-" + shortName);
}

public static Builder with() {
return new Builder();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand Down Expand Up @@ -102,11 +103,16 @@ public ParsedInput parse(String input) {
}
else { // use next word as option value
if (nextWord == null || isOption(nextWord) || isArgumentSeparator(nextWord)) {
throw new IllegalArgumentException("Option '" + currentWord + "' requires a value");
if (!isBooleanOption(commandName, currentWord)) {
throw new IllegalArgumentException("Option '" + currentWord + "' requires a value");
}
nextWord = "true";
}
else {
i++; // skip next word as it was used as option value
}
CommandOption commandOption = parseOption(currentWord + "=" + nextWord);
parsedInputBuilder.addOption(commandOption);
i++; // skip next word as it was used as option value
}
}
else {
Expand Down Expand Up @@ -171,4 +177,13 @@ private String unquoteAndUnescapeQuoted(String s) {
return s;
}

private boolean isBooleanOption(String commandName, String currentWord) {
return Optional.ofNullable(commandRegistry.getCommandByName(commandName))
.map(Command::getOptions)
.orElse(List.of())
.stream()
.filter(o -> o.isOptionEqual(currentWord))
.anyMatch(o -> o.type() == boolean.class || o.type() == Boolean.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,51 @@ static Stream<Arguments> parseWithQuotedArgumentData() {
Arguments.of("mycommand -- value", "value"), Arguments.of("mycommand -- \"value\"", "value"));
}

@ParameterizedTest
@MethodSource("parseWithBooleanOptionData")
void testParseWithBooleanOption(String input, String longName, char shortName, Class<?> type,
String expectedValue) {
// given
Command command = createCommand("mycommand", "My test command");
command.getOptions().add(CommandOption.with().longName(longName).shortName(shortName).type(type).build());
commandRegistry.registerCommand(command);
// when
ParsedInput parsedInput = parser.parse(input);

// then
assertEquals("mycommand", parsedInput.commandName());
assertEquals(1, parsedInput.options().size());
assertEquals(longName, parsedInput.options().get(0).longName());
assertEquals(shortName, parsedInput.options().get(0).shortName());
assertEquals(expectedValue, parsedInput.options().get(0).value());
}

static Stream<Arguments> parseWithBooleanOptionData() {
return Stream.of(Arguments.of("mycommand --option=true", "option", ' ', boolean.class, "true"),
Arguments.of("mycommand --option=false", "option", ' ', boolean.class, "false"),
Arguments.of("mycommand --option true", "option", ' ', boolean.class, "true"),
Arguments.of("mycommand --option false", "option", ' ', boolean.class, "false"),
Arguments.of("mycommand --option", "option", ' ', boolean.class, "true"),

Arguments.of("mycommand -on=true", "", 'o', boolean.class, "true"),
Arguments.of("mycommand -o=false", "", 'o', boolean.class, "false"),
Arguments.of("mycommand -o true", "", 'o', boolean.class, "true"),
Arguments.of("mycommand -o false", "", 'o', boolean.class, "false"),
Arguments.of("mycommand -o", "", 'o', boolean.class, "true"),

Arguments.of("mycommand --option=true", "option", ' ', Boolean.class, "true"),
Arguments.of("mycommand --option=false", "option", ' ', Boolean.class, "false"),
Arguments.of("mycommand --option true", "option", ' ', Boolean.class, "true"),
Arguments.of("mycommand --option false", "option", ' ', Boolean.class, "false"),
Arguments.of("mycommand --option", "option", ' ', Boolean.class, "true"),

Arguments.of("mycommand -on=true", "", 'o', Boolean.class, "true"),
Arguments.of("mycommand -o=false", "", 'o', Boolean.class, "false"),
Arguments.of("mycommand -o true", "", 'o', Boolean.class, "true"),
Arguments.of("mycommand -o false", "", 'o', Boolean.class, "false"),
Arguments.of("mycommand -o", "", 'o', Boolean.class, "true"));
}

private static Command createCommand(String name, String description) {
return new AbstractCommand(name, description) {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ private boolean isOptionPresent(ParsedLine line, CommandOption option) {
CommandOption option;
if (reversed.get(0).isEmpty()) {
// the option name was completed, but no value provided ---> "--optionName "
option = findOption(options, o -> isOptionEqual(reversed.get(1), o));
option = findOption(options, o -> o.isOptionEqual(reversed.get(1)));
}
else {
// the option uses key-value pair ---> "--optionName=someValue"
option = findOption(options, o -> isOptionStartWith(reversed.get(0), o));

// the option uses completion on the value level ---> "--optionName someValue"
if (option == null) {
option = findOption(options, o -> isOptionEqual(reversed.get(1), o));
option = findOption(options, o -> o.isOptionEqual(reversed.get(1)));
}
}

Expand All @@ -135,13 +135,8 @@ private boolean isOptionPresent(ParsedLine line, CommandOption option) {
return options.stream().filter(optionFilter).findFirst().orElse(null);
}

private static boolean isOptionEqual(String optionName, CommandOption option) {
return option.longName() != null && optionName.equals("--" + option.longName())
|| option.shortName() != ' ' && optionName.equals("-" + option.shortName());
}

private static boolean isOptionStartWith(String optionName, CommandOption option) {
return option.longName() != null && optionName.startsWith("--" + option.longName() + "=")
return StringUtils.hasLength(option.longName()) && optionName.startsWith("--" + option.longName() + "=")
|| option.shortName() != ' ' && optionName.startsWith("-" + option.shortName() + "=");
}

Expand Down