Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding mandatory flag to string input #844

Open
wants to merge 4 commits into
base: 2.1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.jline.utils.AttributedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.shell.component.StringInput.StringInputContext;
import org.springframework.shell.component.context.ComponentContext;
import org.springframework.shell.component.support.AbstractTextComponent;
Expand All @@ -44,21 +43,28 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex
private final String defaultValue;
private StringInputContext currentContext;
private Character maskCharacter;
private boolean required;

public StringInput(Terminal terminal) {
this(terminal, null, null, null);
this(terminal, null, null, null, false);
}

public StringInput(Terminal terminal, String name, String defaultValue) {
this(terminal, name, defaultValue, null);
this(terminal, name, defaultValue, null, false);
}

public StringInput(Terminal terminal, String name, String defaultValue,
Function<StringInputContext, List<AttributedString>> renderer) {
this(terminal, name, defaultValue, renderer, false);
}

public StringInput(Terminal terminal, String name, String defaultValue,
Function<StringInputContext, List<AttributedString>> renderer, boolean required) {
super(terminal, name, null);
setRenderer(renderer != null ? renderer : new DefaultRenderer());
setTemplateLocation("classpath:org/springframework/shell/component/string-input-default.stg");
this.defaultValue = defaultValue;
this.required = required;
}

/**
Expand All @@ -70,12 +76,21 @@ public void setMaskCharacter(Character maskCharacter) {
this.maskCharacter = maskCharacter;
}

/**
* Sets a required flag to check that the result is not empty
*
* @param required if input is required
*/
public void setRequired(boolean required) {
this.required = required;
}

@Override
public StringInputContext getThisContext(ComponentContext<?> context) {
if (context != null && currentContext == context) {
return currentContext;
}
currentContext = StringInputContext.of(defaultValue, maskCharacter);
currentContext = StringInputContext.of(defaultValue, maskCharacter, required);
currentContext.setName(getName());
context.stream().forEach(e -> {
currentContext.put(e.getKey(), e.getValue());
Expand Down Expand Up @@ -116,6 +131,9 @@ protected boolean read(BindingReader bindingReader, KeyMap<String> keyMap, Strin
}
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:
Expand Down Expand Up @@ -175,13 +193,27 @@ public interface StringInputContext extends TextComponentContext<String, StringI
*/
Character getMaskCharacter();

/**
* 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 StringInputContext}.
*
* @return empty path input context
*/
public static StringInputContext empty() {
return of(null, null);
return of(null, null, false);
}

/**
Expand All @@ -190,7 +222,16 @@ public static StringInputContext empty() {
* @return path input context
*/
public static StringInputContext of(String defaultValue, Character maskCharacter) {
return new DefaultStringInputContext(defaultValue, maskCharacter);
return of(defaultValue, maskCharacter, false);
}

/**
* Gets an {@link StringInputContext}.
*
* @return path input context
*/
public static StringInputContext of(String defaultValue, Character maskCharacter, boolean required) {
return new DefaultStringInputContext(defaultValue, maskCharacter, required);
}
}

Expand All @@ -199,10 +240,12 @@ private static class DefaultStringInputContext extends BaseTextComponentContext<

private String defaultValue;
private Character maskCharacter;
private boolean required;

public DefaultStringInputContext(String defaultValue, Character maskCharacter) {
public DefaultStringInputContext(String defaultValue, Character maskCharacter, boolean required) {
this.defaultValue = defaultValue;
this.maskCharacter = maskCharacter;
this.required = required;
}

@Override
Expand All @@ -220,6 +263,11 @@ public void setMaskCharacter(Character maskCharacter) {
this.maskCharacter = maskCharacter;
}

@Override
public void setRequired(boolean required) {
this.required = required;
}

@Override
public String getMaskedInput() {
return maybeMask(getInput());
Expand All @@ -240,6 +288,11 @@ public Character getMaskCharacter() {
return maskCharacter;
}

@Override
public boolean isRequired() {
return required;
}

@Override
public Map<String, Object> toTemplateModel() {
Map<String, Object> attributes = super.toTemplateModel();
Expand All @@ -248,6 +301,7 @@ public Map<String, Object> toTemplateModel() {
attributes.put("maskedResultValue", getMaskedResultValue());
attributes.put("maskCharacter", getMaskCharacter());
attributes.put("hasMaskCharacter", hasMaskCharacter());
attributes.put("required", isRequired());
Map<String, Object> model = new HashMap<>();
model.put("model", attributes);
return model;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.util.function.Function;

import org.jline.utils.AttributedString;

import org.springframework.shell.component.StringInput.StringInputContext;
import org.springframework.shell.component.flow.ComponentFlow.BaseBuilder;
import org.springframework.shell.component.flow.ComponentFlow.Builder;
Expand All @@ -38,6 +37,7 @@ public abstract class BaseStringInput extends BaseInput<StringInputSpec> impleme
private ResultMode resultMode;
private String defaultValue;
private Character maskCharacter;
private boolean required = false;
private Function<StringInputContext, List<AttributedString>> renderer;
private List<Consumer<StringInputContext>> preHandlers = new ArrayList<>();
private List<Consumer<StringInputContext>> postHandlers = new ArrayList<>();
Expand Down Expand Up @@ -79,6 +79,12 @@ public StringInputSpec maskCharacter(Character maskCharacter) {
return this;
}

@Override
public StringInputSpec required() {
this.required = true;
return this;
}

@Override
public StringInputSpec renderer(Function<StringInputContext, List<AttributedString>> renderer) {
this.renderer = renderer;
Expand Down Expand Up @@ -146,6 +152,10 @@ public Character getMaskCharacter() {
return maskCharacter;
}

public boolean isRequired() {
return required;
}

public Function<StringInputContext, List<AttributedString>> getRenderer() {
return renderer;
}
Expand All @@ -169,4 +179,4 @@ public boolean isStoreResult() {
public Function<StringInputContext, String> getNext() {
return next;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,18 @@
import org.jline.terminal.Terminal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.io.ResourceLoader;
import org.springframework.shell.component.ConfirmationInput;
import org.springframework.shell.component.ConfirmationInput.ConfirmationInputContext;
import org.springframework.shell.component.MultiItemSelector;
import org.springframework.shell.component.MultiItemSelector.MultiItemSelectorContext;
import org.springframework.shell.component.PathInput;
import org.springframework.shell.component.PathInput.PathInputContext;
import org.springframework.shell.component.SingleItemSelector;
import org.springframework.shell.component.SingleItemSelector.SingleItemSelectorContext;
import org.springframework.shell.component.StringInput;
import org.springframework.shell.component.ConfirmationInput.ConfirmationInputContext;
import org.springframework.shell.component.StringInput.StringInputContext;
import org.springframework.shell.component.context.ComponentContext;
import org.springframework.shell.component.support.SelectorItem;
Expand Down Expand Up @@ -445,7 +444,7 @@ else if (n.isPresent()) {

private Stream<OrderedInputOperation> stringInputsStream() {
return stringInputs.stream().map(input -> {
StringInput selector = new StringInput(terminal, input.getName(), input.getDefaultValue());
StringInput selector = new StringInput(terminal, input.getName(), input.getDefaultValue(), null, input.isRequired());
Function<ComponentContext<?>, ComponentContext<?>> operation = (context) -> {
if (input.getResultMode() == ResultMode.ACCEPT && input.isStoreResult()
&& StringUtils.hasText(input.getResultValue())) {
Expand All @@ -465,6 +464,7 @@ private Stream<OrderedInputOperation> stringInputsStream() {
if (input.getResultMode() == ResultMode.VERIFY && StringUtils.hasText(input.getResultValue())) {
selector.addPreRunHandler(c -> {
c.setDefaultValue(input.getResultValue());
c.setRequired(input.isRequired());
});
}
selector.addPostRunHandler(c -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.util.function.Function;

import org.jline.utils.AttributedString;

import org.springframework.shell.component.StringInput.StringInputContext;
import org.springframework.shell.component.context.ComponentContext;
import org.springframework.shell.component.flow.ComponentFlow.Builder;
Expand Down Expand Up @@ -72,6 +71,13 @@ public interface StringInputSpec extends BaseInputSpec<StringInputSpec> {
*/
StringInputSpec maskCharacter(Character maskCharacter);

/**
* Sets input to required
*
* @return a builder
*/
StringInputSpec required();

/**
* Sets a renderer function.
*
Expand Down Expand Up @@ -128,4 +134,4 @@ public interface StringInputSpec extends BaseInputSpec<StringInputSpec> {
* @return the parent builder
*/
Builder and();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
// message
message(model) ::= <%
<if(model.message && model.hasMessageLevelError)>
<({<figures.error>}); format="style-level-error"> <model.message; format="style-level-error">
<elseif(model.message && model.hasMessageLevelWarn)>
<({<figures.warning>}); format="style-level-warn"> <model.message; format="style-level-warn">
<elseif(model.message && model.hasMessageLevelInfo)>
<({<figures.info>}); format="style-level-info"> <model.message; format="style-level-info">
<endif>
%>

// info section after '? xxx'
info(model) ::= <%
<if(model.hasMaskCharacter)>
Expand All @@ -6,6 +17,8 @@ info(model) ::= <%
<else>
<if(model.defaultValue)>
<("[Default "); format="style-value"><model.defaultValue; format="style-value"><("]"); format="style-value">
<elseif(model.required)>
<("[Required]"); format="style-value">
<endif>
<endif>
<else>
Expand All @@ -14,6 +27,8 @@ info(model) ::= <%
<else>
<if(model.defaultValue)>
<("[Default "); format="style-value"><model.defaultValue; format="style-value"><("]"); format="style-value">
<elseif(model.required)>
<("[Required]"); format="style-value">
<endif>
<endif>
<endif>
Expand All @@ -32,6 +47,7 @@ result(model) ::= <<
// component is running
running(model) ::= <<
<question_name(model)> <info(model)>
<message(model)>
>>

// main
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.springframework.shell.component;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
Expand All @@ -28,13 +30,10 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.shell.component.StringInput.StringInputContext;
import org.springframework.shell.component.context.ComponentContext;

import static org.assertj.core.api.Assertions.assertThat;

public class StringInputTests extends AbstractShellTests {

private ExecutorService service;
Expand Down Expand Up @@ -166,6 +165,39 @@ public void testResultUserInput() throws InterruptedException {
assertThat(run1Context.getResultValue()).isEqualTo("test");
}

@Test
public void testResultMandatoryInput() throws InterruptedException {
ComponentContext<?> empty = ComponentContext.empty();
StringInput component1 = new StringInput(getTerminal());
component1.setResourceLoader(new DefaultResourceLoader());
component1.setTemplateExecutor(getTemplateExecutor());
component1.setRequired(true);

service.execute(() -> {
StringInputContext run1Context = component1.run(empty);
result1.set(run1Context);
latch1.countDown();
});

TestBuffer testBuffer = new TestBuffer().cr();
write(testBuffer.getBytes());

latch1.await(2, TimeUnit.SECONDS);

StringInputContext run1Context = result1.get();
assertThat(consoleOut()).contains("This field is mandatory");
assertThat(run1Context).isNull();

testBuffer.append("test").cr();
write(testBuffer.getBytes());

latch1.await(2, TimeUnit.SECONDS);
run1Context = result1.get();

assertThat(run1Context).isNotNull();
assertThat(run1Context.getResultValue()).isEqualTo("test");
}

@Test
public void testPassingViaContext() throws InterruptedException {
ComponentContext<?> empty = ComponentContext.empty();
Expand Down
Loading