Skip to content

Commit dc6512d

Browse files
author
Nicola Di Falco
committed
feat: adding required on number input
1 parent 4da8016 commit dc6512d

File tree

10 files changed

+141
-9
lines changed

10 files changed

+141
-9
lines changed

spring-shell-core/src/main/java/org/springframework/shell/component/NumberInput.java

+56-7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class NumberInput extends AbstractTextComponent<Number, NumberInputContex
4444
private static final Logger log = LoggerFactory.getLogger(NumberInput.class);
4545
private final Number defaultValue;
4646
private Class<? extends Number> clazz;
47+
private boolean required;
4748
private NumberInputContext currentContext;
4849

4950
public NumberInput(Terminal terminal) {
@@ -55,32 +56,41 @@ public NumberInput(Terminal terminal, String name) {
5556
}
5657

5758
public NumberInput(Terminal terminal, String name, Number defaultValue) {
58-
this(terminal, name, defaultValue, Integer.class, null);
59+
this(terminal, name, defaultValue, Integer.class);
5960
}
6061

6162
public NumberInput(Terminal terminal, String name, Number defaultValue, Class<? extends Number> clazz) {
62-
this(terminal, name, defaultValue, clazz, null);
63+
this(terminal, name, defaultValue, clazz, false);
6364
}
6465

65-
public NumberInput(Terminal terminal, String name, Number defaultValue, Class<? extends Number> clazz,
66+
public NumberInput(Terminal terminal, String name, Number defaultValue, Class<? extends Number> clazz, boolean required) {
67+
this(terminal, name, defaultValue, clazz, required, null);
68+
}
69+
70+
public NumberInput(Terminal terminal, String name, Number defaultValue, Class<? extends Number> clazz, boolean required,
6671
Function<NumberInputContext, List<AttributedString>> renderer) {
6772
super(terminal, name, null);
6873
setRenderer(renderer != null ? renderer : new DefaultRenderer());
6974
setTemplateLocation("classpath:org/springframework/shell/component/number-input-default.stg");
7075
this.defaultValue = defaultValue;
7176
this.clazz = clazz;
77+
this.required = required;
7278
}
7379

7480
public void setNumberClass(Class<? extends Number> clazz) {
7581
this.clazz = clazz;
7682
}
7783

84+
public void setRequired(boolean required) {
85+
this.required = required;
86+
}
87+
7888
@Override
7989
public NumberInputContext getThisContext(ComponentContext<?> context) {
8090
if (context != null && currentContext == context) {
8191
return currentContext;
8292
}
83-
currentContext = NumberInputContext.of(defaultValue, clazz);
93+
currentContext = NumberInputContext.of(defaultValue, clazz, required);
8494
currentContext.setName(getName());
8595
Optional.ofNullable(context).map(ComponentContext::stream)
8696
.ifPresent(entryStream -> entryStream.forEach(e -> currentContext.put(e.getKey(), e.getValue())));
@@ -121,6 +131,9 @@ protected boolean read(BindingReader bindingReader, KeyMap<String> keyMap, Numbe
121131
}
122132
else if (context.getDefaultValue() != null) {
123133
context.setResultValue(context.getDefaultValue());
134+
} else if (required) {
135+
context.setMessage("This field is mandatory", TextComponentContext.MessageLevel.ERROR);
136+
break;
124137
}
125138
return true;
126139
default:
@@ -186,6 +199,20 @@ public interface NumberInputContext extends TextComponentContext<Number, NumberI
186199
*/
187200
void setDefaultClass(Class<? extends Number> defaultClass);
188201

202+
/**
203+
* Sets flag for mandatory input.
204+
*
205+
* @param required true if input is required
206+
*/
207+
void setRequired(boolean required);
208+
209+
/**
210+
* Returns flag if input is required.
211+
*
212+
* @return true if input is required, false otherwise
213+
*/
214+
boolean isRequired();
215+
189216
/**
190217
* Gets an empty {@link NumberInputContext}.
191218
*
@@ -201,7 +228,7 @@ public static NumberInputContext empty() {
201228
* @return number input context
202229
*/
203230
public static NumberInputContext of(Number defaultValue) {
204-
return new DefaultNumberInputContext(defaultValue, Integer.class);
231+
return new DefaultNumberInputContext(defaultValue, Integer.class, false);
205232
}
206233

207234
/**
@@ -210,18 +237,29 @@ public static NumberInputContext of(Number defaultValue) {
210237
* @return number input context
211238
*/
212239
public static NumberInputContext of(Number defaultValue, Class<? extends Number> defaultClass) {
213-
return new DefaultNumberInputContext(defaultValue, defaultClass);
240+
return new DefaultNumberInputContext(defaultValue, defaultClass, false);
241+
}
242+
243+
/**
244+
* Gets an {@link NumberInputContext}.
245+
*
246+
* @return number input context
247+
*/
248+
public static NumberInputContext of(Number defaultValue, Class<? extends Number> defaultClass, boolean required) {
249+
return new DefaultNumberInputContext(defaultValue, defaultClass, required);
214250
}
215251
}
216252

217253
private static class DefaultNumberInputContext extends BaseTextComponentContext<Number, NumberInputContext> implements NumberInputContext {
218254

219255
private Number defaultValue;
220256
private Class<? extends Number> defaultClass;
257+
private boolean required;
221258

222-
public DefaultNumberInputContext(Number defaultValue, Class<? extends Number> defaultClass) {
259+
public DefaultNumberInputContext(Number defaultValue, Class<? extends Number> defaultClass, boolean required) {
223260
this.defaultValue = defaultValue;
224261
this.defaultClass = defaultClass;
262+
this.required = required;
225263
}
226264

227265
@Override
@@ -244,11 +282,22 @@ public void setDefaultClass(Class<? extends Number> defaultClass) {
244282
this.defaultClass = defaultClass;
245283
}
246284

285+
@Override
286+
public void setRequired(boolean required) {
287+
this.required = required;
288+
}
289+
290+
@Override
291+
public boolean isRequired() {
292+
return required;
293+
}
294+
247295
@Override
248296
public Map<String, Object> toTemplateModel() {
249297
Map<String, Object> attributes = super.toTemplateModel();
250298
attributes.put("defaultValue", getDefaultValue() != null ? getDefaultValue() : null);
251299
attributes.put("defaultClass", getDefaultClass().getSimpleName());
300+
attributes.put("required", isRequired());
252301
Map<String, Object> model = new HashMap<>();
253302
model.put("model", attributes);
254303
return model;

spring-shell-core/src/main/java/org/springframework/shell/component/flow/BaseNumberInput.java

+11
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public abstract class BaseNumberInput extends BaseInput<NumberInputSpec> impleme
3737
private ResultMode resultMode;
3838
private Number defaultValue;
3939
private Class<? extends Number> clazz = Integer.class;
40+
private boolean required = false;
4041
private Function<NumberInputContext, List<AttributedString>> renderer;
4142
private final List<Consumer<NumberInputContext>> preHandlers = new ArrayList<>();
4243
private final List<Consumer<NumberInputContext>> postHandlers = new ArrayList<>();
@@ -78,6 +79,12 @@ public NumberInputSpec numberClass(Class<? extends Number> clazz) {
7879
return this;
7980
}
8081

82+
@Override
83+
public NumberInputSpec required() {
84+
this.required = true;
85+
return this;
86+
}
87+
8188
@Override
8289
public NumberInputSpec renderer(Function<NumberInputContext, List<AttributedString>> renderer) {
8390
this.renderer = renderer;
@@ -145,6 +152,10 @@ public Class<? extends Number> getNumberClass() {
145152
return clazz;
146153
}
147154

155+
public boolean isRequired() {
156+
return required;
157+
}
158+
148159
public Function<NumberInputContext, List<AttributedString>> getRenderer() {
149160
return renderer;
150161
}

spring-shell-core/src/main/java/org/springframework/shell/component/flow/ComponentFlow.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ private Stream<OrderedInputOperation> stringInputsStream() {
515515

516516
private Stream<OrderedInputOperation> numberInputsStream() {
517517
return numberInputs.stream().map(input -> {
518-
NumberInput selector = new NumberInput(terminal, input.getName(), input.getDefaultValue(), input.getNumberClass());
518+
NumberInput selector = new NumberInput(terminal, input.getName(), input.getDefaultValue(), input.getNumberClass(), input.isRequired());
519519
UnaryOperator<ComponentContext<?>> operation = context -> {
520520
if (input.getResultMode() == ResultMode.ACCEPT && input.isStoreResult()
521521
&& input.getResultValue() != null) {
@@ -533,7 +533,10 @@ private Stream<OrderedInputOperation> numberInputsStream() {
533533
}
534534
if (input.isStoreResult()) {
535535
if (input.getResultMode() == ResultMode.VERIFY && input.getResultValue() != null) {
536-
selector.addPreRunHandler(c -> c.setDefaultValue(input.getResultValue()));
536+
selector.addPreRunHandler(c -> {
537+
c.setDefaultValue(input.getResultValue());
538+
c.setRequired(input.isRequired());
539+
});
537540
}
538541
selector.addPostRunHandler(c -> c.put(input.getId(), c.getResultValue()));
539542
}

spring-shell-core/src/main/java/org/springframework/shell/component/flow/NumberInputSpec.java

+7
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ public interface NumberInputSpec extends BaseInputSpec<NumberInputSpec> {
7171
*/
7272
NumberInputSpec numberClass(Class<? extends Number> clazz);
7373

74+
/**
75+
* Sets input to required
76+
*
77+
* @return a builder
78+
*/
79+
NumberInputSpec required();
80+
7481
/**
7582
* Sets a renderer function.
7683
*

spring-shell-core/src/main/resources/org/springframework/shell/component/number-input-default.stg

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ info(model) ::= <%
1414
<if(model.input)>
1515
<model.input>
1616
<else>
17+
<if(model.required)>
18+
<("[Required]"); format="style-value">
19+
<endif>
1720
<("[Number Type: "); format="style-value"><model.defaultClass; format="style-value"><("]"); format="style-value">
1821
<if(model.defaultValue)>
1922
<("[Default "); format="style-value"><model.defaultValue; format="style-value"><("]"); format="style-value">

spring-shell-core/src/test/java/org/springframework/shell/component/NumberInputTests.java

+33
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,37 @@ public void testResultUserInputInvalidInput() throws InterruptedException, IOExc
245245
assertThat(run1Context).isNotNull();
246246
assertThat(run1Context.getResultValue()).isNull();
247247
}
248+
249+
@Test
250+
public void testResultMandatoryInput() throws InterruptedException {
251+
ComponentContext<?> empty = ComponentContext.empty();
252+
NumberInput component1 = new NumberInput(getTerminal());
253+
component1.setResourceLoader(new DefaultResourceLoader());
254+
component1.setTemplateExecutor(getTemplateExecutor());
255+
component1.setRequired(true);
256+
257+
service.execute(() -> {
258+
NumberInputContext run1Context = component1.run(empty);
259+
result1.set(run1Context);
260+
latch1.countDown();
261+
});
262+
263+
TestBuffer testBuffer = new TestBuffer().cr();
264+
write(testBuffer.getBytes());
265+
266+
latch1.await(2, TimeUnit.SECONDS);
267+
268+
NumberInputContext run1Context = result1.get();
269+
assertThat(consoleOut()).contains("This field is mandatory");
270+
assertThat(run1Context).isNull();
271+
272+
testBuffer.append("2").cr();
273+
write(testBuffer.getBytes());
274+
275+
latch1.await(2, TimeUnit.SECONDS);
276+
run1Context = result1.get();
277+
278+
assertThat(run1Context).isNotNull();
279+
assertThat(run1Context.getResultValue()).isEqualTo(2);
280+
}
248281
}

spring-shell-core/src/test/java/org/springframework/shell/component/flow/ComponentFlowTests.java

+9
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ public void testSimpleFlow() throws InterruptedException {
5959
.defaultValue(20.5)
6060
.numberClass(Double.class)
6161
.and()
62+
.withNumberInput("number3")
63+
.name("Number3")
64+
.required()
65+
.and()
6266
.withPathInput("path1")
6367
.name("Path1")
6468
.and()
@@ -93,6 +97,9 @@ public void testSimpleFlow() throws InterruptedException {
9397
// number2
9498
testBuffer = new TestBuffer().cr();
9599
write(testBuffer.getBytes());
100+
// number3
101+
testBuffer = new TestBuffer().cr().append("5").cr();
102+
write(testBuffer.getBytes());
96103
// path1
97104
testBuffer = new TestBuffer().append("fakedir").cr();
98105
write(testBuffer.getBytes());
@@ -110,13 +117,15 @@ public void testSimpleFlow() throws InterruptedException {
110117
String field2 = inputWizardResult.getContext().get("field2");
111118
Integer number1 = inputWizardResult.getContext().get("number1");
112119
Double number2 = inputWizardResult.getContext().get("number2");
120+
Integer number3 = inputWizardResult.getContext().get("number3");
113121
Path path1 = inputWizardResult.getContext().get("path1");
114122
String single1 = inputWizardResult.getContext().get("single1");
115123
List<String> multi1 = inputWizardResult.getContext().get("multi1");
116124
assertThat(field1).isEqualTo("defaultField1Value");
117125
assertThat(field2).isEqualTo("Field2Value");
118126
assertThat(number1).isEqualTo(35);
119127
assertThat(number2).isEqualTo(20.5);
128+
assertThat(number3).isEqualTo(5);
120129
assertThat(path1.toString()).contains("fakedir");
121130
assertThat(single1).isEqualTo("value1");
122131
assertThat(multi1).containsExactlyInAnyOrder("value2");

spring-shell-docs/src/main/asciidoc/using-shell-components-ui-numberinput.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ The context object is `NumberInputContext`. The following table lists its contex
2828
|`defaultClass`
2929
|The default number class to use, if set. Otherwise, Integer.class.
3030

31+
|`required`
32+
|`true` if the input is required. Otherwise, false.
33+
3134
|`model`
3235
|The parent context variables (see <<textcomponentcontext-template-variables>>).
3336
|===

spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java

+10
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ public String numberInputDouble() {
7575
return "Got value " + context.getResultValue();
7676
}
7777

78+
@ShellMethod(key = "component number required", value = "Number input", group = "Components")
79+
public String numberInputRequired() {
80+
NumberInput component = new NumberInput(getTerminal(), "Enter value");
81+
component.setRequired(true);
82+
component.setResourceLoader(getResourceLoader());
83+
component.setTemplateExecutor(getTemplateExecutor());
84+
NumberInputContext context = component.run(NumberInputContext.empty());
85+
return "Got value " + context.getResultValue();
86+
}
87+
7888
@ShellMethod(key = "component path", value = "Path input", group = "Components")
7989
public String pathInput() {
8090
PathInput component = new PathInput(getTerminal(), "Enter value");

spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public void showcase1() {
6969
.defaultValue(20.5)
7070
.numberClass(Double.class)
7171
.and()
72+
.withNumberInput("number3")
73+
.name("Field3")
74+
.required()
75+
.and()
7276
.withConfirmationInput("confirmation1")
7377
.name("Confirmation1")
7478
.and()

0 commit comments

Comments
 (0)