Components can't use interactive mode without tty

- Adding a concept of no-tty which in this commit simply
  tracks DumbTerminal as jline creates that if there nothing
  better.
- For components without tty don't go to interaction loop.
- For new sample show that we can at least manually handle
  required option with a flow while command option is not
  required.
- Fixes #444
This commit is contained in:
Janne Valkealahti
2022-06-15 07:10:56 +01:00
parent a019934169
commit 195d1d00ab
18 changed files with 349 additions and 9 deletions

View File

@@ -210,6 +210,10 @@ public interface CommandExecution {
this.parserExceptions = parserExceptions;
}
public static CommandParserExceptionsException of(String message, List<CommandParserException> parserExceptions) {
return new CommandParserExceptionsException(message, parserExceptions);
}
public List<CommandParserException> getParserExceptions() {
return parserExceptions;
}

View File

@@ -24,6 +24,8 @@ import org.jline.keymap.BindingReader;
import org.jline.keymap.KeyMap;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.shell.component.ConfirmationInput.ConfirmationInputContext;
import org.springframework.shell.component.context.ComponentContext;
@@ -39,6 +41,7 @@ import org.springframework.util.StringUtils;
*/
public class ConfirmationInput extends AbstractTextComponent<Boolean, ConfirmationInputContext> {
private final static Logger log = LoggerFactory.getLogger(ConfirmationInput.class);
private final boolean defaultValue;
private ConfirmationInputContext currentContext;
@@ -78,6 +81,10 @@ public class ConfirmationInput extends AbstractTextComponent<Boolean, Confirmati
@Override
protected boolean read(BindingReader bindingReader, KeyMap<String> keyMap, ConfirmationInputContext context) {
String operation = bindingReader.readBinding(keyMap);
log.debug("Binding read result {}", operation);
if (operation == null) {
return true;
}
String input;
switch (operation) {
case OPERATION_CHAR:

View File

@@ -70,7 +70,10 @@ public class MultiItemSelector<T, I extends Nameable & Matchable & Enableable &
@Override
protected MultiItemSelectorContext<T, I> runInternal(MultiItemSelectorContext<T, I> context) {
super.runInternal(context);
loop(context);
// if there's no tty don't try to loop as it would then cause user interaction
if (hasTty()) {
loop(context);
}
return context;
}

View File

@@ -27,6 +27,8 @@ import org.jline.keymap.BindingReader;
import org.jline.keymap.KeyMap;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.shell.component.PathInput.PathInputContext;
import org.springframework.shell.component.context.ComponentContext;
@@ -42,6 +44,7 @@ import org.springframework.util.StringUtils;;
*/
public class PathInput extends AbstractTextComponent<Path, PathInputContext> {
private final static Logger log = LoggerFactory.getLogger(PathInput.class);
private PathInputContext currentContext;
private Function<String, Path> pathProvider = (path) -> Paths.get(path);
@@ -75,6 +78,10 @@ public class PathInput extends AbstractTextComponent<Path, PathInputContext> {
@Override
protected boolean read(BindingReader bindingReader, KeyMap<String> keyMap, PathInputContext context) {
String operation = bindingReader.readBinding(keyMap);
log.debug("Binding read result {}", operation);
if (operation == null) {
return true;
}
String input;
switch (operation) {
case OPERATION_CHAR:

View File

@@ -70,7 +70,10 @@ public class SingleItemSelector<T, I extends Nameable & Matchable & Enableable &
@Override
protected SingleItemSelectorContext<T, I> runInternal(SingleItemSelectorContext<T, I> context) {
super.runInternal(context);
loop(context);
// if there's no tty don't try to loop as it would then cause user interaction
if (hasTty()) {
loop(context);
}
return context;
}

View File

@@ -24,6 +24,8 @@ import org.jline.keymap.BindingReader;
import org.jline.keymap.KeyMap;
import org.jline.terminal.Terminal;
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;
@@ -38,6 +40,7 @@ import org.springframework.util.StringUtils;
*/
public class StringInput extends AbstractTextComponent<String, StringInputContext> {
private final static Logger log = LoggerFactory.getLogger(StringInput.class);
private final String defaultValue;
private StringInputContext currentContext;
private Character maskCharacter;
@@ -83,6 +86,10 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex
@Override
protected boolean read(BindingReader bindingReader, KeyMap<String> keyMap, StringInputContext context) {
String operation = bindingReader.readBinding(keyMap);
log.debug("Binding read result {}", operation);
if (operation == null) {
return true;
}
String input;
switch (operation) {
case OPERATION_CHAR:

View File

@@ -56,6 +56,14 @@ public interface ComponentContext<C extends ComponentContext<C>> {
*/
<T> T get(Object key, Class<T> type);
/**
* Check if a context contains a key.
*
* @param key the key
* @return true if context contains key
*/
boolean containsKey(Object key);
/**
* Put an entry into a context.
*

View File

@@ -33,6 +33,7 @@ import org.jline.keymap.KeyMap;
import org.jline.terminal.Attributes;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.impl.DumbTerminal;
import org.jline.utils.AttributedString;
import org.jline.utils.Display;
import org.jline.utils.InfoCmp.Capability;
@@ -153,7 +154,8 @@ public abstract class AbstractComponent<T extends ComponentContext<T>> implement
context = runPreRunHandlers(getThisContext(context));
T run = runInternal(getThisContext(context));
context = runPostRunHandlers(getThisContext(context));
if (printResults) {
// if there's no tty don't try to print results as it'd be pointless
if (printResults && hasTty()) {
printResults(context);
}
return run;
@@ -186,6 +188,22 @@ public abstract class AbstractComponent<T extends ComponentContext<T>> implement
this.templateLocation = templateLocation;
}
/**
* Checks if this component has an existing {@code tty}.
*
* @return true if component has tty
*/
protected boolean hasTty() {
boolean hasTty = true;
if (this.terminal instanceof DumbTerminal) {
if (this.terminal.getSize().getRows() == 0) {
hasTty = false;
}
}
log.debug("Terminal is {} with size {}, marking hasTty as {}", this.terminal, this.terminal.getSize(), hasTty);
return hasTty;
}
/**
* Render a given template with attributes.
*

View File

@@ -26,6 +26,8 @@ import org.jline.keymap.BindingReader;
import org.jline.keymap.KeyMap;
import org.jline.terminal.Terminal;
import org.jline.utils.InfoCmp.Capability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.shell.component.context.BaseComponentContext;
import org.springframework.shell.component.context.ComponentContext;
@@ -46,6 +48,7 @@ import static org.jline.keymap.KeyMap.key;
public abstract class AbstractSelectorComponent<T, C extends SelectorComponentContext<T, I, C>, I extends Nameable & Matchable & Enableable & Itemable<T>>
extends AbstractComponent<C> {
private final static Logger log = LoggerFactory.getLogger(AbstractSelectorComponent.class);
protected final String name;
private final List<I> items;
private Comparator<I> comparator = (o1, o2) -> 0;
@@ -155,6 +158,10 @@ public abstract class AbstractSelectorComponent<T, C extends SelectorComponentCo
ItemStateViewProjection buildItemStateView = buildItemStateView(start.get(), thisContext);
List<ItemState<I>> itemStateView = buildItemStateView.items;
String operation = bindingReader.readBinding(keyMap);
log.debug("Binding read result {}", operation);
if (operation == null) {
return true;
}
String input;
switch (operation) {
case OPERATION_SELECT:

View File

@@ -66,7 +66,10 @@ public abstract class AbstractTextComponent<T, C extends TextComponentContext<T,
@Override
protected C runInternal(C context) {
loop(context);
// if there's no tty don't try to loop as it would then cause user interaction
if (hasTty()) {
loop(context);
}
return context;
}