Выберите из небольшого количества вариантов в Spring Shell

В настоящее время я пытаюсь создать приложение 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 или есть более элегантный способ выполнить этот ввод?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
1 347
3

Ответы 3

Столкнувшись с аналогичным требованием и не найдя примера такой вещи в документах 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 + "\"";
    }
}

Другие вопросы по теме