From dc6512d72316e9ffaecad29cdf07ef7e8c858db3 Mon Sep 17 00:00:00 2001 From: Nicola Di Falco Date: Mon, 7 Aug 2023 13:26:13 +0200 Subject: [PATCH] feat: adding required on number input --- .../shell/component/NumberInput.java | 63 ++++++++++++++++--- .../shell/component/flow/BaseNumberInput.java | 11 ++++ .../shell/component/flow/ComponentFlow.java | 7 ++- .../shell/component/flow/NumberInputSpec.java | 7 +++ .../shell/component/number-input-default.stg | 3 + .../shell/component/NumberInputTests.java | 33 ++++++++++ .../component/flow/ComponentFlowTests.java | 9 +++ ...using-shell-components-ui-numberinput.adoc | 3 + .../samples/standard/ComponentCommands.java | 10 +++ .../standard/ComponentFlowCommands.java | 4 ++ 10 files changed, 141 insertions(+), 9 deletions(-) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/NumberInput.java b/spring-shell-core/src/main/java/org/springframework/shell/component/NumberInput.java index 74c68a9f9..c15f560ad 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/NumberInput.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/NumberInput.java @@ -44,6 +44,7 @@ public class NumberInput extends AbstractTextComponent clazz; + private boolean required; private NumberInputContext currentContext; public NumberInput(Terminal terminal) { @@ -55,32 +56,41 @@ public NumberInput(Terminal terminal, String name) { } public NumberInput(Terminal terminal, String name, Number defaultValue) { - this(terminal, name, defaultValue, Integer.class, null); + this(terminal, name, defaultValue, Integer.class); } public NumberInput(Terminal terminal, String name, Number defaultValue, Class clazz) { - this(terminal, name, defaultValue, clazz, null); + this(terminal, name, defaultValue, clazz, false); } - public NumberInput(Terminal terminal, String name, Number defaultValue, Class clazz, + public NumberInput(Terminal terminal, String name, Number defaultValue, Class clazz, boolean required) { + this(terminal, name, defaultValue, clazz, required, null); + } + + public NumberInput(Terminal terminal, String name, Number defaultValue, Class clazz, boolean required, Function> renderer) { super(terminal, name, null); setRenderer(renderer != null ? renderer : new DefaultRenderer()); setTemplateLocation("classpath:org/springframework/shell/component/number-input-default.stg"); this.defaultValue = defaultValue; this.clazz = clazz; + this.required = required; } public void setNumberClass(Class clazz) { this.clazz = clazz; } + public void setRequired(boolean required) { + this.required = required; + } + @Override public NumberInputContext getThisContext(ComponentContext context) { if (context != null && currentContext == context) { return currentContext; } - currentContext = NumberInputContext.of(defaultValue, clazz); + currentContext = NumberInputContext.of(defaultValue, clazz, required); currentContext.setName(getName()); Optional.ofNullable(context).map(ComponentContext::stream) .ifPresent(entryStream -> entryStream.forEach(e -> currentContext.put(e.getKey(), e.getValue()))); @@ -121,6 +131,9 @@ protected boolean read(BindingReader bindingReader, KeyMap keyMap, Numbe } else if (context.getDefaultValue() != null) { context.setResultValue(context.getDefaultValue()); + } else if (required) { + context.setMessage("This field is mandatory", TextComponentContext.MessageLevel.ERROR); + break; } return true; default: @@ -186,6 +199,20 @@ public interface NumberInputContext extends TextComponentContext defaultClass); + /** + * Sets flag for mandatory input. + * + * @param required true if input is required + */ + void setRequired(boolean required); + + /** + * Returns flag if input is required. + * + * @return true if input is required, false otherwise + */ + boolean isRequired(); + /** * Gets an empty {@link NumberInputContext}. * @@ -201,7 +228,7 @@ public static NumberInputContext empty() { * @return number input context */ public static NumberInputContext of(Number defaultValue) { - return new DefaultNumberInputContext(defaultValue, Integer.class); + return new DefaultNumberInputContext(defaultValue, Integer.class, false); } /** @@ -210,7 +237,16 @@ public static NumberInputContext of(Number defaultValue) { * @return number input context */ public static NumberInputContext of(Number defaultValue, Class defaultClass) { - return new DefaultNumberInputContext(defaultValue, defaultClass); + return new DefaultNumberInputContext(defaultValue, defaultClass, false); + } + + /** + * Gets an {@link NumberInputContext}. + * + * @return number input context + */ + public static NumberInputContext of(Number defaultValue, Class defaultClass, boolean required) { + return new DefaultNumberInputContext(defaultValue, defaultClass, required); } } @@ -218,10 +254,12 @@ private static class DefaultNumberInputContext extends BaseTextComponentContext< private Number defaultValue; private Class defaultClass; + private boolean required; - public DefaultNumberInputContext(Number defaultValue, Class defaultClass) { + public DefaultNumberInputContext(Number defaultValue, Class defaultClass, boolean required) { this.defaultValue = defaultValue; this.defaultClass = defaultClass; + this.required = required; } @Override @@ -244,11 +282,22 @@ public void setDefaultClass(Class defaultClass) { this.defaultClass = defaultClass; } + @Override + public void setRequired(boolean required) { + this.required = required; + } + + @Override + public boolean isRequired() { + return required; + } + @Override public Map toTemplateModel() { Map attributes = super.toTemplateModel(); attributes.put("defaultValue", getDefaultValue() != null ? getDefaultValue() : null); attributes.put("defaultClass", getDefaultClass().getSimpleName()); + attributes.put("required", isRequired()); Map model = new HashMap<>(); model.put("model", attributes); return model; diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/flow/BaseNumberInput.java b/spring-shell-core/src/main/java/org/springframework/shell/component/flow/BaseNumberInput.java index 6a1200cf0..09893293e 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/flow/BaseNumberInput.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/flow/BaseNumberInput.java @@ -37,6 +37,7 @@ public abstract class BaseNumberInput extends BaseInput impleme private ResultMode resultMode; private Number defaultValue; private Class clazz = Integer.class; + private boolean required = false; private Function> renderer; private final List> preHandlers = new ArrayList<>(); private final List> postHandlers = new ArrayList<>(); @@ -78,6 +79,12 @@ public NumberInputSpec numberClass(Class clazz) { return this; } + @Override + public NumberInputSpec required() { + this.required = true; + return this; + } + @Override public NumberInputSpec renderer(Function> renderer) { this.renderer = renderer; @@ -145,6 +152,10 @@ public Class getNumberClass() { return clazz; } + public boolean isRequired() { + return required; + } + public Function> getRenderer() { return renderer; } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/flow/ComponentFlow.java b/spring-shell-core/src/main/java/org/springframework/shell/component/flow/ComponentFlow.java index 501df49de..8692ff0f6 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/flow/ComponentFlow.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/flow/ComponentFlow.java @@ -515,7 +515,7 @@ private Stream stringInputsStream() { private Stream numberInputsStream() { return numberInputs.stream().map(input -> { - NumberInput selector = new NumberInput(terminal, input.getName(), input.getDefaultValue(), input.getNumberClass()); + NumberInput selector = new NumberInput(terminal, input.getName(), input.getDefaultValue(), input.getNumberClass(), input.isRequired()); UnaryOperator> operation = context -> { if (input.getResultMode() == ResultMode.ACCEPT && input.isStoreResult() && input.getResultValue() != null) { @@ -533,7 +533,10 @@ private Stream numberInputsStream() { } if (input.isStoreResult()) { if (input.getResultMode() == ResultMode.VERIFY && input.getResultValue() != null) { - selector.addPreRunHandler(c -> c.setDefaultValue(input.getResultValue())); + selector.addPreRunHandler(c -> { + c.setDefaultValue(input.getResultValue()); + c.setRequired(input.isRequired()); + }); } selector.addPostRunHandler(c -> c.put(input.getId(), c.getResultValue())); } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/flow/NumberInputSpec.java b/spring-shell-core/src/main/java/org/springframework/shell/component/flow/NumberInputSpec.java index 9012a88a2..881206a44 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/flow/NumberInputSpec.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/flow/NumberInputSpec.java @@ -71,6 +71,13 @@ public interface NumberInputSpec extends BaseInputSpec { */ NumberInputSpec numberClass(Class clazz); + /** + * Sets input to required + * + * @return a builder + */ + NumberInputSpec required(); + /** * Sets a renderer function. * diff --git a/spring-shell-core/src/main/resources/org/springframework/shell/component/number-input-default.stg b/spring-shell-core/src/main/resources/org/springframework/shell/component/number-input-default.stg index 128dd072b..cdd4b1a8d 100644 --- a/spring-shell-core/src/main/resources/org/springframework/shell/component/number-input-default.stg +++ b/spring-shell-core/src/main/resources/org/springframework/shell/component/number-input-default.stg @@ -14,6 +14,9 @@ info(model) ::= <% + + <("[Required]"); format="style-value"> + <("[Number Type: "); format="style-value"><("]"); format="style-value"> <("[Default "); format="style-value"><("]"); format="style-value"> diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/NumberInputTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/NumberInputTests.java index 99514522a..d27153baf 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/NumberInputTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/NumberInputTests.java @@ -245,4 +245,37 @@ public void testResultUserInputInvalidInput() throws InterruptedException, IOExc assertThat(run1Context).isNotNull(); assertThat(run1Context.getResultValue()).isNull(); } + + @Test + public void testResultMandatoryInput() throws InterruptedException { + ComponentContext empty = ComponentContext.empty(); + NumberInput component1 = new NumberInput(getTerminal()); + component1.setResourceLoader(new DefaultResourceLoader()); + component1.setTemplateExecutor(getTemplateExecutor()); + component1.setRequired(true); + + service.execute(() -> { + NumberInputContext run1Context = component1.run(empty); + result1.set(run1Context); + latch1.countDown(); + }); + + TestBuffer testBuffer = new TestBuffer().cr(); + write(testBuffer.getBytes()); + + latch1.await(2, TimeUnit.SECONDS); + + NumberInputContext run1Context = result1.get(); + assertThat(consoleOut()).contains("This field is mandatory"); + assertThat(run1Context).isNull(); + + testBuffer.append("2").cr(); + write(testBuffer.getBytes()); + + latch1.await(2, TimeUnit.SECONDS); + run1Context = result1.get(); + + assertThat(run1Context).isNotNull(); + assertThat(run1Context.getResultValue()).isEqualTo(2); + } } diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/flow/ComponentFlowTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/flow/ComponentFlowTests.java index 7ebe29e15..3fcb13413 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/flow/ComponentFlowTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/flow/ComponentFlowTests.java @@ -59,6 +59,10 @@ public void testSimpleFlow() throws InterruptedException { .defaultValue(20.5) .numberClass(Double.class) .and() + .withNumberInput("number3") + .name("Number3") + .required() + .and() .withPathInput("path1") .name("Path1") .and() @@ -93,6 +97,9 @@ public void testSimpleFlow() throws InterruptedException { // number2 testBuffer = new TestBuffer().cr(); write(testBuffer.getBytes()); + // number3 + testBuffer = new TestBuffer().cr().append("5").cr(); + write(testBuffer.getBytes()); // path1 testBuffer = new TestBuffer().append("fakedir").cr(); write(testBuffer.getBytes()); @@ -110,6 +117,7 @@ public void testSimpleFlow() throws InterruptedException { String field2 = inputWizardResult.getContext().get("field2"); Integer number1 = inputWizardResult.getContext().get("number1"); Double number2 = inputWizardResult.getContext().get("number2"); + Integer number3 = inputWizardResult.getContext().get("number3"); Path path1 = inputWizardResult.getContext().get("path1"); String single1 = inputWizardResult.getContext().get("single1"); List multi1 = inputWizardResult.getContext().get("multi1"); @@ -117,6 +125,7 @@ public void testSimpleFlow() throws InterruptedException { assertThat(field2).isEqualTo("Field2Value"); assertThat(number1).isEqualTo(35); assertThat(number2).isEqualTo(20.5); + assertThat(number3).isEqualTo(5); assertThat(path1.toString()).contains("fakedir"); assertThat(single1).isEqualTo("value1"); assertThat(multi1).containsExactlyInAnyOrder("value2"); diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-components-ui-numberinput.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-components-ui-numberinput.adoc index 923851bae..ddeac010e 100644 --- a/spring-shell-docs/src/main/asciidoc/using-shell-components-ui-numberinput.adoc +++ b/spring-shell-docs/src/main/asciidoc/using-shell-components-ui-numberinput.adoc @@ -28,6 +28,9 @@ The context object is `NumberInputContext`. The following table lists its contex |`defaultClass` |The default number class to use, if set. Otherwise, Integer.class. +|`required` +|`true` if the input is required. Otherwise, false. + |`model` |The parent context variables (see <>). |=== diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java index f1bf2d3db..a876d1314 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java @@ -75,6 +75,16 @@ public String numberInputDouble() { return "Got value " + context.getResultValue(); } + @ShellMethod(key = "component number required", value = "Number input", group = "Components") + public String numberInputRequired() { + NumberInput component = new NumberInput(getTerminal(), "Enter value"); + component.setRequired(true); + component.setResourceLoader(getResourceLoader()); + component.setTemplateExecutor(getTemplateExecutor()); + NumberInputContext context = component.run(NumberInputContext.empty()); + return "Got value " + context.getResultValue(); + } + @ShellMethod(key = "component path", value = "Path input", group = "Components") public String pathInput() { PathInput component = new PathInput(getTerminal(), "Enter value"); diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java index 69d8dddda..8610e545b 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java @@ -69,6 +69,10 @@ public void showcase1() { .defaultValue(20.5) .numberClass(Double.class) .and() + .withNumberInput("number3") + .name("Field3") + .required() + .and() .withConfirmationInput("confirmation1") .name("Confirmation1") .and()