Skip to content
Closed
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 @@ -49,20 +49,28 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex

private @Nullable Character maskCharacter;

private boolean required;

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

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

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

public StringInput(Terminal terminal, @Nullable String name, @Nullable String defaultValue,
@Nullable 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 @@ -73,12 +81,21 @@ public void setMaskCharacter(@Nullable 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(@Nullable ComponentContext<?> context) {
if (context != null && currentContext == context) {
return currentContext;
}
currentContext = StringInputContext.of(defaultValue, maskCharacter);
currentContext = StringInputContext.of(defaultValue, maskCharacter, required);
currentContext.setName(getName());
if (context != null) {
context.stream().forEach(e -> {
Expand Down Expand Up @@ -124,6 +141,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 @@ -176,20 +196,43 @@ public interface StringInputContext extends TextComponentContext<String, StringI
*/
@Nullable 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);
}

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

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

}
Expand All @@ -201,9 +244,12 @@ private static class DefaultStringInputContext extends BaseTextComponentContext<

private @Nullable Character maskCharacter;

public DefaultStringInputContext(@Nullable String defaultValue, @Nullable Character maskCharacter) {
private boolean required;

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

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

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

@Override
public @Nullable String getMaskedInput() {
return maybeMask(getInput());
Expand All @@ -241,6 +292,11 @@ public boolean hasMaskCharacter() {
return maskCharacter;
}

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

@Override
public Map<String, @Nullable Object> toTemplateModel() {
Map<String, @Nullable Object> attributes = super.toTemplateModel();
Expand All @@ -249,6 +305,7 @@ public boolean hasMaskCharacter() {
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 @@ -45,6 +45,8 @@ public abstract class BaseStringInput extends BaseInput<StringInputSpec> impleme

private @Nullable Character maskCharacter;

private boolean required = false;

private @Nullable Function<StringInputContext, List<AttributedString>> renderer;

private List<Consumer<StringInputContext>> preHandlers = new ArrayList<>();
Expand Down Expand Up @@ -91,6 +93,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 @@ -158,6 +166,10 @@ public StringInputSpec getThis() {
return maskCharacter;
}

public boolean isRequired() {
return required;
}

public @Nullable Function<StringInputContext, List<AttributedString>> getRenderer() {
return renderer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,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 @@ -492,6 +492,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 @@ -67,6 +67,13 @@ public interface StringInputSpec extends BaseInputSpec<StringInputSpec> {
*/
StringInputSpec maskCharacter(Character maskCharacter);

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

/**
* Sets a renderer function.
* @param renderer the renderer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,39 @@ void testResultUserInput() {
});
}

@Test
void testResultMandatoryInput() {
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);
});

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

await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> {
StringInputContext run1Context = result1.get();
assertThat(consoleOut()).contains("This field is mandatory");
assertThat(run1Context).isNull();
});

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

await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> {
StringInputContext run1Context = result1.get();

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

@Test
void testResultUserInputUnicode() {
ComponentContext<?> empty = ComponentContext.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ void testSimpleFlow() {
.withStringInput("field2")
.name("Field2")
.and()
.withStringInput("field3")
.name("Field3")
.required()
.and()
.withPathInput("path1")
.name("Path1")
.and()
Expand All @@ -78,6 +82,9 @@ void testSimpleFlow() {
// field2
testBuffer = new TestBuffer().append("Field2Value").cr();
write(testBuffer.getBytes());
// field3
testBuffer = new TestBuffer().cr().append("Field3Value").cr();
write(testBuffer.getBytes());
// path1
testBuffer = new TestBuffer().append("fakedir").cr();
write(testBuffer.getBytes());
Expand All @@ -93,17 +100,18 @@ void testSimpleFlow() {
assertThat(inputWizardResult).isNotNull();
String field1 = inputWizardResult.getContext().get("field1");
String field2 = inputWizardResult.getContext().get("field2");
String field3 = inputWizardResult.getContext().get("field3");
Path path1 = inputWizardResult.getContext().get("path1");
String single1 = inputWizardResult.getContext().get("single1");
List<String> multi1 = inputWizardResult.getContext().get("multi1");
assertThat(field1).isEqualTo("defaultField1Value");
assertThat(field3).isEqualTo("Field3Value");
assertThat(field2).isEqualTo("Field2Value");
assertThat(path1.toString()).contains("fakedir");
assertThat(single1).isEqualTo("value1");
assertThat(multi1).containsExactlyInAnyOrder("value2");
assertThat(consoleOut()).contains("Field1 defaultField1Value");
});

}

@Test
Expand Down
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 @@ -4,7 +4,8 @@
ifndef::snippets[:snippets: ../../../../../src/test/java/org/springframework/shell/docs]

The string input component asks a user for simple text input, optionally masking values
if the content contains something sensitive. The following listing shows an example:
if the content contains something sensitive. The input can also be required (at least 1 char). +
The following listing shows an example:

[source, java, indent=0]
----
Expand Down Expand Up @@ -40,6 +41,9 @@ The context object is `StringInputContext`. The following table lists its contex
|`hasMaskCharacter`
|`true` if a mask character is set. Otherwise, false.

|`required`
|`true` if the input is required. Otherwise, false.

|`model`
|The parent context variables (see xref:/components/ui/render.adoc#textcomponentcontext-template-variables[TextComponentContext Template Variables]).
|===
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public void showcase1() {
.withStringInput("field2")
.name("Field2")
.and()
.withStringInput("field3")
.name("Field3")
.mandatory()
.and()
.withConfirmationInput("confirmation1")
.name("Confirmation1")
.and()
Expand Down