Skip to content

Commit 624343a

Browse files
committed
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. - Fixes #630
1 parent 56b9cb2 commit 624343a

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

spring-shell-core/src/main/java/org/springframework/shell/command/CommandParser.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
import java.util.ArrayDeque;
1919
import java.util.ArrayList;
2020
import java.util.Arrays;
21+
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.Comparator;
2324
import java.util.Deque;
2425
import java.util.List;
26+
import java.util.Set;
2527
import java.util.stream.Collectors;
2628
import java.util.stream.Stream;
2729

@@ -466,6 +468,13 @@ private ConvertArgumentsHolder convertArguments(CommandOption option, List<Strin
466468
else if (type != null && type.isArray()) {
467469
value = arguments.stream().collect(Collectors.toList()).toArray();
468470
}
471+
// if it looks like type is a collection just get as list
472+
// as conversion will happen later. we just need to know
473+
// if user has Set, List, Collection, etc without worrying
474+
// about generics.
475+
else if (type != null && type.asCollection() != ResolvableType.NONE) {
476+
value = arguments.stream().collect(Collectors.toList());
477+
}
469478
else {
470479
if (!arguments.isEmpty()) {
471480
if (arguments.size() == 1) {

spring-shell-core/src/test/java/org/springframework/shell/command/CommandParserTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,39 @@ public void testLongOptionsWithArray() {
310310
assertThat(results.results().get(0).value()).isEqualTo(new int[] { 1, 2 });
311311
}
312312

313+
@Test
314+
public void testLongOptionsWithStringArray() {
315+
CommandOption option1 = longOption("arg1", ResolvableType.forType(String[].class));
316+
List<CommandOption> options = Arrays.asList(option1);
317+
String[] args = new String[]{"--arg1", "1", "2"};
318+
CommandParserResults results = parser.parse(options, args);
319+
assertThat(results.results()).hasSize(1);
320+
assertThat(results.results().get(0).option()).isSameAs(option1);
321+
assertThat(results.results().get(0).value()).isEqualTo(new String[] { "1", "2" });
322+
}
323+
324+
@Test
325+
public void testLongOptionsWithPlainList() {
326+
CommandOption option1 = longOption("arg1", ResolvableType.forType(List.class));
327+
List<CommandOption> options = Arrays.asList(option1);
328+
String[] args = new String[]{"--arg1", "1", "2"};
329+
CommandParserResults results = parser.parse(options, args);
330+
assertThat(results.results()).hasSize(1);
331+
assertThat(results.results().get(0).option()).isSameAs(option1);
332+
assertThat(results.results().get(0).value()).isEqualTo(Arrays.asList("1", "2"));
333+
}
334+
335+
@Test
336+
public void testLongOptionsWithTypedList() {
337+
CommandOption option1 = longOption("arg1", ResolvableType.forClassWithGenerics(List.class, String.class));
338+
List<CommandOption> options = Arrays.asList(option1);
339+
String[] args = new String[]{"--arg1", "1", "2"};
340+
CommandParserResults results = parser.parse(options, args);
341+
assertThat(results.results()).hasSize(1);
342+
assertThat(results.results().get(0).option()).isSameAs(option1);
343+
assertThat(results.results().get(0).value()).isEqualTo(Arrays.asList("1", "2"));
344+
}
345+
313346
@Test
314347
public void testArityErrors() {
315348
CommandOption option1 = CommandOption.of(

spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionTypeCommands.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
package org.springframework.shell.samples.e2e;
1717

1818
import java.io.PrintWriter;
19+
import java.util.Collection;
20+
import java.util.List;
21+
import java.util.Set;
1922

2023
import org.springframework.context.annotation.Bean;
2124
import org.springframework.shell.command.CommandRegistration;
@@ -289,6 +292,96 @@ public CommandRegistration optionTypeIntArrayRegistration(CommandRegistration.Bu
289292
.build();
290293
}
291294

295+
//
296+
// List<String>
297+
//
298+
299+
@ShellMethod(key = LEGACY_ANNO + "option-type-string-list", group = GROUP)
300+
public String optionTypeStringListAnnotation(
301+
@ShellOption(help = "Desc arg1") List<String> arg1
302+
) {
303+
return "Hello " + arg1;
304+
}
305+
306+
@Bean
307+
public CommandRegistration optionTypeStringListRegistration(CommandRegistration.BuilderSupplier builder) {
308+
return builder.get()
309+
.command(REG, "option-type-string-list")
310+
.group(GROUP)
311+
.withOption()
312+
.longNames("arg1")
313+
.type(List.class)
314+
.required()
315+
.and()
316+
.withTarget()
317+
.function(ctx -> {
318+
List<String> arg1 = ctx.getOptionValue("arg1");
319+
return "Hello " + arg1;
320+
})
321+
.and()
322+
.build();
323+
}
324+
325+
//
326+
// Set<String>
327+
//
328+
329+
@ShellMethod(key = LEGACY_ANNO + "option-type-string-set", group = GROUP)
330+
public String optionTypeStringSetAnnotation(
331+
@ShellOption(help = "Desc arg1") Set<String> arg1
332+
) {
333+
return "Hello " + arg1;
334+
}
335+
336+
@Bean
337+
public CommandRegistration optionTypeStringSetRegistration(CommandRegistration.BuilderSupplier builder) {
338+
return builder.get()
339+
.command(REG, "option-type-string-set")
340+
.group(GROUP)
341+
.withOption()
342+
.longNames("arg1")
343+
.type(Set.class)
344+
.required()
345+
.and()
346+
.withTarget()
347+
.function(ctx -> {
348+
Set<String> arg1 = ctx.getOptionValue("arg1");
349+
return "Hello " + arg1;
350+
})
351+
.and()
352+
.build();
353+
}
354+
355+
//
356+
// Collection<String>
357+
//
358+
359+
@ShellMethod(key = LEGACY_ANNO + "option-type-string-collection", group = GROUP)
360+
public String optionTypeStringCollectionAnnotation(
361+
@ShellOption(help = "Desc arg1") Collection<String> arg1
362+
) {
363+
return "Hello " + arg1;
364+
}
365+
366+
@Bean
367+
public CommandRegistration optionTypeStringCollectionRegistration(CommandRegistration.BuilderSupplier builder) {
368+
return builder.get()
369+
.command(REG, "option-type-string-collection")
370+
.group(GROUP)
371+
.withOption()
372+
.longNames("arg1")
373+
.type(Collection.class)
374+
.required()
375+
.and()
376+
.withTarget()
377+
.function(ctx -> {
378+
Collection<String> arg1 = ctx.getOptionValue("arg1");
379+
return "Hello " + arg1;
380+
})
381+
.and()
382+
.build();
383+
}
384+
292385
//
293386
// Void
294387
//

0 commit comments

Comments
 (0)