В настоящее время я пытаюсь создать приложение cli с помощью Spring Shell.
Я хочу, чтобы пользователь мог быстро выбрать один из 2-3 вариантов. Мой текущий код отлично работает в eclipse, но когда я запускаю его в Powershell, мне приходится нажимать Enter несколько раз (как минимум 3 раза), чтобы выбрать опцию. После однократного нажатия Enter ничего не происходит.
Мой текущий метод:
@ShellMethod(key = { "setService", "select" }, value = "Choose a Speech to Text Service")
public void setService() {
boolean success = false;
do {
this.console.write("Please select a speech recognition service. Type in the number and press enter:");
this.console.write("1. Google Speech API");
this.console.write("2. Bing Speech API");
// Get Input
Scanner s = new Scanner(System.in);
String input = s.nextLine();
// Select Service
switch (input) {
case "1":
/*
* do something...
*/
console.write("Google Speech API selected");
success = true;
break;
case "2":
/*
* do something...
*/
console.write("Bing Speech API selected");
success = true;
break;
default:
this.console.error("Input not valid. Please type a number and press Enter to select a Service");
break;
}
} while (!success);
}
Как я могу исправить проблему с помощью PowerShell или есть более элегантный способ выполнить этот ввод?




Столкнувшись с аналогичным требованием и не найдя примера такой вещи в документах JLine 3 или Spring-Shell 2, я немного поэкспериментировал ...
По сути, эти компоненты не предназначены для такой работы. Попытка получить дополнительный ввод пользователя, уже находясь в контексте @ShellMethod, просто не поддерживается.
Любые операции ввода-вывода, которые вы могли бы выполнять с помощью System.out или System.in, в конечном итоге обрабатываются JLine по-разному «под капотом» в зависимости от того, из какого типа оболочки вы впервые начали (отсюда ваше наблюдение, что ваша попытка ведет себя по-разному, когда приложение вызывается из PowerShell по сравнению с запускается из среды IDE). Мне удалось добиться желаемого поведения, если я запустил из GitBash в Windows, но не из CMD. В одном случае я просто закончил тем, что полностью разбил приложение.
Однако я нашел один способ запрашивать ввод, который работал как для GitBash, так и для CMD. Этот пример не будет работать в обычном JLine, но, похоже, работает в Spring-Shell 2.0, если вы используете автоконфигурацию Spring Boot (что дает вам доступ к тому же экземпляру LineReader, который Spring инициализировал для вашего приложения оболочки).
@Autowired
LineReader reader;
public String ask(String question) {
return this.reader.readLine("\n" + question + " > ");
}
@ShellMethod(key = { "setService", "select" }, value = "Choose a Speech to Text Service")
public void setService() {
boolean success = false;
do {
String question = "Please select a speech recognition service. Type in the number and press enter:"
+ "\n 1. Google Speech API"
+ "\n 2. Bing Speech API";
// Get Input
String input = this.ask(question);
// Select Service
switch (input) {
case "1":
/*
* do something...
*/
console.write("Google Speech API selected");
success = true;
break;
case "2":
/*
* do something...
*/
console.write("Bing Speech API selected");
success = true;
break;
default:
this.console.error("Input not valid. Please type a number and press Enter to select a Service");
break;
}
} while (!success);
}
Я использую TextIO для этого, чтобы сделать что-то подобное. В частности, я использую метод read на [IntInputReader][2], который удобен, как указано в его документах:
Reads a value of type T. It repeatedly prompts the users to enter the value, until they provide a valid input string.
Поскольку мне много раз требовалась эта функциональность в моем приложении Spring Shell (чтобы пользователи могли выбирать из разных типов объектов), я написал общий метод, который
Presents the user with a list of items of a given type {@code T} and prompts them to select one or more. Optionally, once selection is complete, the user is presented with a summary of their selection for their confirmation.
К сожалению, StackOverflow разрушает мою документацию Javadoc, поэтому я создал Gist с полностью документированным методом.
public <T> List<T> cliObjectSelector(List<T> items, boolean allowMultiple,
boolean requireConfirmation, @Nullable String customPromptMessage,
Function<T, String> f) throws OperationCancelledException {
if (items == null || items.isEmpty()) {
textIO.getTextTerminal().print("No items found. Cannot continue");
throw new OperationCancelledException("The provided list of items is empty");
}
String userPrompt = (customPromptMessage != null) ? customPromptMessage + ":\n" :
String.format("Please select one of the %d items from the list:\n", items.size());
List<String> optionsList = items.stream()
.map(item -> {
return String.format("[%d] - %s", items.indexOf(item), f.apply(item));
}).collect(Collectors.toList());
List<T> selectedItems = new ArrayList<>();
optionsList.add(0, userPrompt);
while(true) {
textIO.getTextTerminal().println(optionsList);
T selected = items.get(
textIO.newIntInputReader()
.withMinVal(0)
.withMaxVal(items.size() - 1)
.read("Option number"));
selectedItems.add(selected);
if (allowMultiple == false) {
break;
}
if ( ! askYesNo("Select another item?")) {
break;
}
}
if (requireConfirmation == false) {
return selectedItems;
}
if (confirmSelection(selectedItems.stream()
.map(item -> f.apply(item))
.collect(Collectors.toList()))) {
return selectedItems;
} else {
throw new OperationCancelledException();
}
}
Пример использования:
@ShellMethod(
key = "select-something",
value = "Select something")
public void select() {
Instance selected = cliObjectSelector(instances, false, requireConfirmation,
"Please select one of the " + instances.size() + " instances from the list",
instance -> instance.getX().getName()).get(0);
}
Spring Shell 2 основан на JLine 3. Для «небольшого количества опций» я бы рекомендовал использовать такую функцию, как завершение табуляции.
(Он работает на Powershell, но я никогда не получаю поддержки завершения табуляции в Eclipse-Console).
Это довольно просто:
import java.util.List;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.completer.StringsCompleter;
import org.jline.terminal.Terminal;
import org.springframework.context.annotation.Lazy;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
@ShellComponent
public class CompleterInteractionCommand {
private final List<String> OPTIONS = List.of("create", "update", "delete");
private final Terminal terminal;
public CompleterInteractionCommand(@Lazy final Terminal terminal) {
this.terminal = terminal;
}
@ShellMethod
public String completeSelect() {
LineReader lineReader = LineReaderBuilder.builder()
.terminal(this.terminal)
.completer(new StringsCompleter(this.OPTIONS))
.build();
/* Important: This allows completion on an empty buffer, rather than inserting a tab! */
lineReader.unsetOpt(LineReader.Option.INSERT_TAB);
String desription = "select on of this options: " + OPTIONS + "\n"
+ " use TAB (twice) to select them\n";
String input = lineReader.readLine(desription + "input: ").trim();
return "you selected \"" + input + "\"";
}
}