Skip to content

Commit eaa363b

Browse files
committed
Add better support for modified option names
- Longnames in a command option if modified via name modifier didn't provide enough backmapping info for command execution experience being accurate. - Add new getLongNamesModified() into CommandOption which is populated if name modifier is used. - Add more hints in CommandExecution for modified option names. - This should bring annotation and programmatic commands up to date. - Backport #777 - Fixes #782
1 parent 21fc455 commit eaa363b

File tree

7 files changed

+68
-16
lines changed

7 files changed

+68
-16
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 the original author or authors.
2+
* Copyright 2022-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -148,8 +148,9 @@ private Optional<CommandParserResult> find(String name) {
148148
return results.results().stream()
149149
.filter(r -> {
150150
Stream<String> l = Arrays.asList(r.option().getLongNames()).stream();
151+
Stream<String> lm = Arrays.asList(r.option().getLongNamesModified()).stream();
151152
Stream<String> s = Arrays.asList(r.option().getShortNames()).stream().map(n -> Character.toString(n));
152-
return Stream.concat(l, s)
153+
return Stream.concat(Stream.concat(l, lm), s)
153154
.filter(o -> ObjectUtils.nullSafeEquals(o, name))
154155
.findFirst()
155156
.isPresent();

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ else if (targetInfo.getTargetType() == TargetType.METHOD) {
198198
messageBuilder.setHeader(ArgumentHeaderMethodArgumentResolver.ARGUMENT_PREFIX + n, r.value());
199199
paramValues.put(n, r.value());
200200
}
201+
// need to provide backmapping for orinal names which were modified
202+
for (String n : r.option().getLongNamesModified()) {
203+
messageBuilder.setHeader(ArgumentHeaderMethodArgumentResolver.ARGUMENT_PREFIX + n, r.value());
204+
paramValues.put(n, r.value());
205+
}
201206
}
202207
if (r.option().getShortNames() != null) {
203208
for (Character n : r.option().getShortNames()) {

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 the original author or authors.
2+
* Copyright 2022-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,14 @@ public interface CommandOption {
3232
*/
3333
String[] getLongNames();
3434

35+
/**
36+
* Gets a modified long names of an option. Set within a command registration if
37+
* option name modifier were used to have an info about original names.
38+
*
39+
* @return modified long names of an option
40+
*/
41+
String[] getLongNamesModified();
42+
3543
/**
3644
* Gets a short names of an option.
3745
*
@@ -111,7 +119,7 @@ public interface CommandOption {
111119
* @return default command option
112120
*/
113121
public static CommandOption of(String[] longNames, Character[] shortNames, String description) {
114-
return of(longNames, shortNames, description, null, false, null, null, null, null, null, null);
122+
return of(longNames, null, shortNames, description, null, false, null, null, null, null, null, null);
115123
}
116124

117125
/**
@@ -125,13 +133,14 @@ public static CommandOption of(String[] longNames, Character[] shortNames, Strin
125133
*/
126134
public static CommandOption of(String[] longNames, Character[] shortNames, String description,
127135
ResolvableType type) {
128-
return of(longNames, shortNames, description, type, false, null, null, null, null, null, null);
136+
return of(longNames, null, shortNames, description, type, false, null, null, null, null, null, null);
129137
}
130138

131139
/**
132140
* Gets an instance of a default {@link CommandOption}.
133141
*
134142
* @param longNames the long names
143+
* @param longNamesModified the modified long names
135144
* @param shortNames the short names
136145
* @param description the description
137146
* @param type the type
@@ -144,10 +153,10 @@ public static CommandOption of(String[] longNames, Character[] shortNames, Strin
144153
* @param completion the completion
145154
* @return default command option
146155
*/
147-
public static CommandOption of(String[] longNames, Character[] shortNames, String description,
156+
public static CommandOption of(String[] longNames, String[] longNamesModified, Character[] shortNames, String description,
148157
ResolvableType type, boolean required, String defaultValue, Integer position, Integer arityMin,
149158
Integer arityMax, String label, CompletionResolver completion) {
150-
return new DefaultCommandOption(longNames, shortNames, description, type, required, defaultValue, position,
159+
return new DefaultCommandOption(longNames, longNamesModified, shortNames, description, type, required, defaultValue, position,
151160
arityMin, arityMax, label, completion);
152161
}
153162

@@ -157,6 +166,7 @@ public static CommandOption of(String[] longNames, Character[] shortNames, Strin
157166
public static class DefaultCommandOption implements CommandOption {
158167

159168
private String[] longNames;
169+
private String[] longNamesModified;
160170
private Character[] shortNames;
161171
private String description;
162172
private ResolvableType type;
@@ -168,11 +178,12 @@ public static class DefaultCommandOption implements CommandOption {
168178
private String label;
169179
private CompletionResolver completion;
170180

171-
public DefaultCommandOption(String[] longNames, Character[] shortNames, String description,
181+
public DefaultCommandOption(String[] longNames, String[] longNamesModified, Character[] shortNames, String description,
172182
ResolvableType type, boolean required, String defaultValue, Integer position,
173183
Integer arityMin, Integer arityMax, String label,
174184
CompletionResolver completion) {
175185
this.longNames = longNames != null ? longNames : new String[0];
186+
this.longNamesModified = longNamesModified != null ? longNamesModified : new String[0];
176187
this.shortNames = shortNames != null ? shortNames : new Character[0];
177188
this.description = description;
178189
this.type = type;
@@ -190,6 +201,11 @@ public String[] getLongNames() {
190201
return longNames;
191202
}
192203

204+
@Override
205+
public String[] getLongNamesModified() {
206+
return longNamesModified;
207+
}
208+
193209
@Override
194210
public Character[] getShortNames() {
195211
return shortNames;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1245,11 +1245,13 @@ public List<CommandOption> getOptions() {
12451245
options = optionSpecs.stream()
12461246
.map(o -> {
12471247
String[] longNames = o.getLongNames();
1248+
String[] longNamesModified = null;
12481249
Function<String, String> modifier = o.getOptionNameModifier();
12491250
if (modifier != null) {
1251+
longNamesModified = Arrays.copyOf(longNames, longNames.length);
12501252
longNames = Arrays.stream(longNames).map(modifier).toArray(String[]::new);
12511253
}
1252-
return CommandOption.of(longNames, o.getShortNames(), o.getDescription(), o.getType(),
1254+
return CommandOption.of(longNames, longNamesModified, o.getShortNames(), o.getDescription(), o.getType(),
12531255
o.isRequired(), o.getDefaultValue(), o.getPosition(), o.getArityMin(), o.getArityMax(),
12541256
o.getLabel(), o.getCompletion());
12551257
})

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,4 +539,21 @@ public void testCommandNotAvailable() {
539539
Object result = execution.evaluate(r1, new String[]{"--arg1", "myarg1value"});
540540
assertThat(result).isInstanceOf(CommandNotCurrentlyAvailable.class);
541541
}
542+
543+
@Test
544+
public void testExecutionWithModifiedLongOption() {
545+
CommandRegistration r1 = CommandRegistration.builder()
546+
.command("command1")
547+
.withOption()
548+
.longNames("arg1")
549+
.nameModifier(orig -> "x" + orig)
550+
.and()
551+
.withTarget()
552+
.function(function1)
553+
.and()
554+
.build();
555+
Object result = execution.evaluate(r1, new String[] { "command1", "--xarg1", "myarg1value" });
556+
assertThat(result).isEqualTo("himyarg1value");
557+
}
558+
542559
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ public void testArityErrors() {
351351
new String[] { "arg1" },
352352
null,
353353
null,
354+
null,
354355
ResolvableType.forType(int[].class),
355356
true,
356357
null,
@@ -385,6 +386,7 @@ public void testMapToIntArray() {
385386
new String[] { "arg1" },
386387
null,
387388
null,
389+
null,
388390
ResolvableType.forType(int[].class),
389391
false,
390392
null,
@@ -439,6 +441,7 @@ public void testMapPositionalArgsWhenTypeList() {
439441
new String[] { "arg1" },
440442
null,
441443
null,
444+
null,
442445
ResolvableType.forType(List.class),
443446
true,
444447
null,
@@ -474,7 +477,7 @@ public void testMapPositionalArgs2() {
474477
@Test
475478
public void testBooleanWithDefault() {
476479
ResolvableType type = ResolvableType.forType(boolean.class);
477-
CommandOption option1 = CommandOption.of(new String[] { "arg1" }, new Character[0], "description", type, false,
480+
CommandOption option1 = CommandOption.of(new String[] { "arg1" }, null, new Character[0], "description", type, false,
478481
"true", null, null, null, null, null);
479482

480483
List<CommandOption> options = Arrays.asList(option1);
@@ -488,7 +491,7 @@ public void testBooleanWithDefault() {
488491
@Test
489492
public void testIntegerWithDefault() {
490493
ResolvableType type = ResolvableType.forType(Integer.class);
491-
CommandOption option1 = CommandOption.of(new String[] { "arg1" }, new Character[0], "description", type, false,
494+
CommandOption option1 = CommandOption.of(new String[] { "arg1" }, null, new Character[0], "description", type, false,
492495
"1", null, null, null, null, null);
493496

494497
List<CommandOption> options = Arrays.asList(option1);
@@ -502,7 +505,7 @@ public void testIntegerWithDefault() {
502505
@Test
503506
public void testIntegerWithGivenValue() {
504507
ResolvableType type = ResolvableType.forType(Integer.class);
505-
CommandOption option1 = CommandOption.of(new String[] { "arg1" }, new Character[0], "description", type, false,
508+
CommandOption option1 = CommandOption.of(new String[] { "arg1" }, null, new Character[0], "description", type, false,
506509
null, null, null, null, null, null);
507510

508511
List<CommandOption> options = Arrays.asList(option1);
@@ -586,6 +589,7 @@ public void testPositionDoesNotAffectRequiredErrorWithOtherErrors() {
586589
null,
587590
null,
588591
null,
592+
null,
589593
true,
590594
null,
591595
0,
@@ -623,7 +627,7 @@ private static CommandOption longOption(String name, ResolvableType type, boolea
623627
}
624628

625629
private static CommandOption longOption(String name, ResolvableType type, boolean required, Integer position, Integer arityMin, Integer arityMax) {
626-
return CommandOption.of(new String[] { name }, new Character[0], "desc", type, required, null, position,
630+
return CommandOption.of(new String[] { name }, null, new Character[0], "desc", type, required, null, position,
627631
arityMin, arityMax, null, null);
628632
}
629633

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ public class OptionNamingCommands {
2828
public static class LegacyAnnotation extends BaseE2ECommands {
2929

3030
@ShellMethod(key = LEGACY_ANNO + "option-naming-1", group = GROUP)
31-
public void testOptionNaming1Annotation(
31+
public String testOptionNaming1Annotation(
3232
@ShellOption("from_snake") String snake,
3333
@ShellOption("fromCamel") String camel,
3434
@ShellOption("from-kebab") String kebab,
3535
@ShellOption("FromPascal") String pascal
3636
) {
37+
return String.format("snake='%s' camel='%s' kebab='%s' pascal='%s' ", snake, camel, kebab, pascal);
3738
}
3839

3940
}
@@ -65,10 +66,16 @@ public CommandRegistration testOptionNaming1Registration() {
6566
.withOption()
6667
.longNames("arg1")
6768
.nameModifier(name -> "x" + name)
68-
.required()
69+
// .required()
6970
.and()
7071
.withTarget()
71-
.consumer(ctx -> {})
72+
.function(ctx -> {
73+
String snake = ctx.getOptionValue("from_snake");
74+
String camel = ctx.getOptionValue("fromCamel");
75+
String kebab = ctx.getOptionValue("from-kebab");
76+
String pascal = ctx.getOptionValue("FromPascal");
77+
return String.format("snake='%s' camel='%s' kebab='%s' pascal='%s' ", snake, camel, kebab, pascal);
78+
})
7279
.and()
7380
.build();
7481
}

0 commit comments

Comments
 (0)