Handle collection types in a parser

- Handle any option collection type so that list is generated for
  values, this then works well when actual type conversions happen.
- Backport #630
- Fixes #631
This commit is contained in:
Janne Valkealahti
2023-01-19 17:33:18 +00:00
parent 78da071e86
commit 1bc26c7e17
3 changed files with 135 additions and 0 deletions

View File

@@ -18,10 +18,12 @@ package org.springframework.shell.command;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -466,6 +468,13 @@ public interface CommandParser {
else if (type != null && type.isArray()) {
value = arguments.stream().collect(Collectors.toList()).toArray();
}
// if it looks like type is a collection just get as list
// as conversion will happen later. we just need to know
// if user has Set, List, Collection, etc without worrying
// about generics.
else if (type != null && type.asCollection() != ResolvableType.NONE) {
value = arguments.stream().collect(Collectors.toList());
}
else {
if (!arguments.isEmpty()) {
if (arguments.size() == 1) {

View File

@@ -310,6 +310,39 @@ public class CommandParserTests extends AbstractCommandTests {
assertThat(results.results().get(0).value()).isEqualTo(new int[] { 1, 2 });
}
@Test
public void testLongOptionsWithStringArray() {
CommandOption option1 = longOption("arg1", ResolvableType.forType(String[].class));
List<CommandOption> options = Arrays.asList(option1);
String[] args = new String[]{"--arg1", "1", "2"};
CommandParserResults results = parser.parse(options, args);
assertThat(results.results()).hasSize(1);
assertThat(results.results().get(0).option()).isSameAs(option1);
assertThat(results.results().get(0).value()).isEqualTo(new String[] { "1", "2" });
}
@Test
public void testLongOptionsWithPlainList() {
CommandOption option1 = longOption("arg1", ResolvableType.forType(List.class));
List<CommandOption> options = Arrays.asList(option1);
String[] args = new String[]{"--arg1", "1", "2"};
CommandParserResults results = parser.parse(options, args);
assertThat(results.results()).hasSize(1);
assertThat(results.results().get(0).option()).isSameAs(option1);
assertThat(results.results().get(0).value()).isEqualTo(Arrays.asList("1", "2"));
}
@Test
public void testLongOptionsWithTypedList() {
CommandOption option1 = longOption("arg1", ResolvableType.forClassWithGenerics(List.class, String.class));
List<CommandOption> options = Arrays.asList(option1);
String[] args = new String[]{"--arg1", "1", "2"};
CommandParserResults results = parser.parse(options, args);
assertThat(results.results()).hasSize(1);
assertThat(results.results().get(0).option()).isSameAs(option1);
assertThat(results.results().get(0).value()).isEqualTo(Arrays.asList("1", "2"));
}
@Test
public void testArityErrors() {
CommandOption option1 = CommandOption.of(

View File

@@ -16,6 +16,9 @@
package org.springframework.shell.samples.e2e;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.springframework.context.annotation.Bean;
import org.springframework.shell.command.CommandRegistration;
@@ -289,6 +292,96 @@ public class OptionTypeCommands extends BaseE2ECommands {
.build();
}
//
// List<String>
//
@ShellMethod(key = LEGACY_ANNO + "option-type-string-list", group = GROUP)
public String optionTypeStringListAnnotation(
@ShellOption(help = "Desc arg1") List<String> arg1
) {
return "Hello " + arg1;
}
@Bean
public CommandRegistration optionTypeStringListRegistration(CommandRegistration.BuilderSupplier builder) {
return builder.get()
.command(REG, "option-type-string-list")
.group(GROUP)
.withOption()
.longNames("arg1")
.type(List.class)
.required()
.and()
.withTarget()
.function(ctx -> {
List<String> arg1 = ctx.getOptionValue("arg1");
return "Hello " + arg1;
})
.and()
.build();
}
//
// Set<String>
//
@ShellMethod(key = LEGACY_ANNO + "option-type-string-set", group = GROUP)
public String optionTypeStringSetAnnotation(
@ShellOption(help = "Desc arg1") Set<String> arg1
) {
return "Hello " + arg1;
}
@Bean
public CommandRegistration optionTypeStringSetRegistration(CommandRegistration.BuilderSupplier builder) {
return builder.get()
.command(REG, "option-type-string-set")
.group(GROUP)
.withOption()
.longNames("arg1")
.type(Set.class)
.required()
.and()
.withTarget()
.function(ctx -> {
Set<String> arg1 = ctx.getOptionValue("arg1");
return "Hello " + arg1;
})
.and()
.build();
}
//
// Collection<String>
//
@ShellMethod(key = LEGACY_ANNO + "option-type-string-collection", group = GROUP)
public String optionTypeStringCollectionAnnotation(
@ShellOption(help = "Desc arg1") Collection<String> arg1
) {
return "Hello " + arg1;
}
@Bean
public CommandRegistration optionTypeStringCollectionRegistration(CommandRegistration.BuilderSupplier builder) {
return builder.get()
.command(REG, "option-type-string-collection")
.group(GROUP)
.withOption()
.longNames("arg1")
.type(Collection.class)
.required()
.and()
.withTarget()
.function(ctx -> {
Collection<String> arg1 = ctx.getOptionValue("arg1");
return "Hello " + arg1;
})
.and()
.build();
}
//
// Void
//