Skip to content

Expose default option in SingleItemSelector #415

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

Merged
merged 1 commit into from
May 13, 2022
Merged
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 @@ -41,6 +41,7 @@ public abstract class BaseSingleItemSelector extends BaseInput<SingleItemSelecto
private String resultValue;
private ResultMode resultMode;
private Map<String, String> selectItems = new HashMap<>();
private String defaultSelect;
private Comparator<SelectorItem<String>> comparator;
private Function<SingleItemSelectorContext<String, SelectorItem<String>>, List<AttributedString>> renderer;
private Integer maxItems;
Expand Down Expand Up @@ -84,6 +85,12 @@ public SingleItemSelectorSpec selectItems(Map<String, String> selectItems) {
return this;
}

@Override
public SingleItemSelectorSpec defaultSelect(String name) {
this.defaultSelect = name;
return this;
}

@Override
public SingleItemSelectorSpec sort(Comparator<SelectorItem<String>> comparator) {
this.comparator = comparator;
Expand Down Expand Up @@ -160,6 +167,10 @@ public Map<String, String> getSelectItems() {
return selectItems;
}

public String getDefaultSelect() {
return defaultSelect;
}

public Comparator<SelectorItem<String>> getComparator() {
return comparator;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,20 @@ private Stream<OrderedInputOperation> singleItemSelectorsStream() {
List<SelectorItem<String>> selectorItems = input.getSelectItems().entrySet().stream()
.map(e -> SelectorItem.of(e.getKey(), e.getValue()))
.collect(Collectors.toList());

// setup possible item for initial expose
String defaultSelect = input.getDefaultSelect();
Stream<SelectorItem<String>> defaultCheckStream = StringUtils.hasText(defaultSelect)
? selectorItems.stream()
: Stream.empty();
SelectorItem<String> defaultExpose = defaultCheckStream
.filter(si -> ObjectUtils.nullSafeEquals(defaultSelect, si.getName()))
.findFirst()
.orElse(null);

SingleItemSelector<String, SelectorItem<String>> selector = new SingleItemSelector<>(terminal,
selectorItems, input.getName(), input.getComparator());
selector.setDefaultExpose(defaultExpose);
Function<ComponentContext<?>, ComponentContext<?>> operation = (context) -> {
if (input.getResultMode() == ResultMode.ACCEPT && input.isStoreResult()
&& StringUtils.hasText(input.getResultValue())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ public interface SingleItemSelectorSpec extends BaseInputSpec<SingleItemSelector
*/
SingleItemSelectorSpec selectItems(Map<String, String> selectItems);

/**
* Automatically selects and exposes a given item.
*
* @param name the name
* @return a builder
*/
SingleItemSelectorSpec defaultSelect(String name);

/**
* Sets a {@link Comparator} for sorting items.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.shell.component.context.ComponentContext;
import org.springframework.shell.component.support.AbstractSelectorComponent.SelectorComponentContext;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import static org.jline.keymap.KeyMap.ctrl;
Expand All @@ -54,6 +55,8 @@ public abstract class AbstractSelectorComponent<T, C extends SelectorComponentCo
private boolean stale = false;
private AtomicInteger start = new AtomicInteger(0);
private AtomicInteger pos = new AtomicInteger(0);
private I defaultExpose;
private boolean expose = false;

public AbstractSelectorComponent(Terminal terminal, String name, List<I> items, boolean exitSelects,
Comparator<I> comparator) {
Expand Down Expand Up @@ -95,6 +98,18 @@ public Function<T, String> getItemMapper() {
return itemMapper;
}

/**
* Sets default expose item when component start.
*
* @param defaultExpose the default item
*/
public void setDefaultExpose(I defaultExpose) {
this.defaultExpose = defaultExpose;
if (defaultExpose != null) {
expose = true;
}
}

/**
* Gets items.
*
Expand All @@ -120,6 +135,7 @@ protected void bindKeyMap(KeyMap<String> keyMap) {
@Override
protected C runInternal(C context) {
C thisContext = getThisContext(context);
initialExpose(thisContext);
ItemStateViewProjection buildItemStateView = buildItemStateView(start.get(), thisContext);
List<ItemState<I>> itemStateView = buildItemStateView.items;
thisContext.setItemStateView(itemStateView);
Expand Down Expand Up @@ -224,6 +240,33 @@ else if (start.get() + pos.get() <= 0) {
return false;
}

private void initialExpose(C context) {
if (!expose) {
return;
}
expose = false;
List<ItemState<I>> itemStates = context.getItemStates();
if (itemStates == null) {
AtomicInteger index = new AtomicInteger(0);
itemStates = context.getItems().stream()
.sorted(comparator)
.map(item -> ItemState.of(item, item.getName(), index.getAndIncrement(), item.isEnabled()))
.collect(Collectors.toList());
}
for (int i = 0; i < itemStates.size(); i++) {
if (ObjectUtils.nullSafeEquals(itemStates.get(i).getName(), defaultExpose.getName())) {
if (i < maxItems) {
this.pos.set(i);
}
else {
this.pos.set(maxItems - 1);
this.start.set(i - maxItems + 1);
}
break;
}
}
}

private ItemStateViewProjection buildItemStateView(int skip, SelectorComponentContext<T, I, ?> context) {
List<ItemState<I>> itemStates = context.getItemStates();
if (itemStates == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,56 +154,89 @@ public void testSkipsGivenComponents() throws InterruptedException {
assertThat(id4).containsExactlyInAnyOrder("value4");
}

@Test
public void testChoosesDynamically() throws InterruptedException {
ComponentFlow wizard = ComponentFlow.builder()
@Test
public void testChoosesDynamically() throws InterruptedException {
ComponentFlow wizard = ComponentFlow.builder()
.terminal(getTerminal())
.resourceLoader(getResourceLoader())
.templateExecutor(getTemplateExecutor())
.withStringInput("id1")
.name("name")
.next(ctx -> ctx.get("id1"))
.and()
.withStringInput("id2")
.name("name")
.resultValue("value2")
.resultMode(ResultMode.ACCEPT)
.next(ctx -> null)
.and()
.withStringInput("id3")
.name("name")
.resultValue("value3")
.resultMode(ResultMode.ACCEPT)
.next(ctx -> null)
.and()
.build();

ExecutorService service = Executors.newFixedThreadPool(1);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<ComponentFlowResult> result = new AtomicReference<>();
service.execute(() -> {
result.set(wizard.run());
latch.countDown();
});

// id1
TestBuffer testBuffer = new TestBuffer().append("id3").cr();
write(testBuffer.getBytes());
// id3
testBuffer = new TestBuffer().cr();
write(testBuffer.getBytes());
latch.await(4, TimeUnit.SECONDS);
ComponentFlowResult inputWizardResult = result.get();
assertThat(inputWizardResult).isNotNull();
String id1 = inputWizardResult.getContext().get("id1");
// TODO: should be able to check if variable exists
// String id2 = inputWizardResult.getContext().get("id2");
String id3 = inputWizardResult.getContext().get("id3");
assertThat(id1).isEqualTo("id3");
// assertThat(id2).isNull();
assertThat(id3).isEqualTo("value3");
}

@Test
public void testAutoShowsDefault() throws InterruptedException {
Map<String, String> single1SelectItems = new HashMap<>();
single1SelectItems.put("key1", "value1");
single1SelectItems.put("key2", "value2");
ComponentFlow wizard = ComponentFlow.builder()
.terminal(getTerminal())
.resourceLoader(getResourceLoader())
.templateExecutor(getTemplateExecutor())
.withStringInput("id1")
.name("name")
.next(ctx -> ctx.get("id1"))
.and()
.withStringInput("id2")
.name("name")
.resultValue("value2")
.resultMode(ResultMode.ACCEPT)
.next(ctx -> null)
.and()
.withStringInput("id3")
.name("name")
.resultValue("value3")
.resultMode(ResultMode.ACCEPT)
.next(ctx -> null)
.withSingleItemSelector("single1")
.name("Single1")
.selectItems(single1SelectItems)
.defaultSelect("key2")
.and()
.build();

ExecutorService service = Executors.newFixedThreadPool(1);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<ComponentFlowResult> result = new AtomicReference<>();

service.execute(() -> {
result.set(wizard.run());
latch.countDown();
});

// id1
TestBuffer testBuffer = new TestBuffer().append("id3").cr();
write(testBuffer.getBytes());

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

latch.await(4, TimeUnit.SECONDS);
ComponentFlowResult inputWizardResult = result.get();
assertThat(inputWizardResult).isNotNull();
String id1 = inputWizardResult.getContext().get("id1");
// TODO: should be able to check if variable exists
// String id2 = inputWizardResult.getContext().get("id2");
String id3 = inputWizardResult.getContext().get("id3");
assertThat(id1).isEqualTo("id3");
// assertThat(id2).isNull();
assertThat(id3).isEqualTo("value3");
}
ExecutorService service = Executors.newFixedThreadPool(1);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<ComponentFlowResult> result = new AtomicReference<>();

service.execute(() -> {
result.set(wizard.run());
latch.countDown();
});

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

latch.await(4, TimeUnit.SECONDS);
ComponentFlowResult inputWizardResult = result.get();
assertThat(inputWizardResult).isNotNull();
String single1 = inputWizardResult.getContext().get("single1");
assertThat(single1).isEqualTo("value2");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.component.flow.ComponentFlow;
import org.springframework.shell.component.flow.SelectItem;
import org.springframework.shell.standard.AbstractShellComponent;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class ComponentFlowCommands extends AbstractShellComponent {
Expand Down Expand Up @@ -90,4 +93,23 @@ public void conditional() {
.build();
flow.run();
}

@ShellMethod(key = "flow autoselect", value = "Autoselect item", group = "Flow")
public void autoselect(
@ShellOption(defaultValue = "Field3") String defaultValue
) {
Map<String, String> single1SelectItems = IntStream.range(1, 10)
.boxed()
.collect(Collectors.toMap(i -> "Field" + i, i -> "field" + i));

ComponentFlow flow = componentFlowBuilder.clone().reset()
.withSingleItemSelector("single1")
.name("Single1")
.selectItems(single1SelectItems)
.defaultSelect(defaultValue)
.sort((o1, o2) -> o1.getName().compareTo(o2.getName()))
.and()
.build();
flow.run();
}
}