1

我目前正在尝试使用 Spring Shell 制作一个 cli 应用程序。

我希望用户能够快速选择 2-3 个选项之一。我当前的代码在 Eclipse 中运行良好,但是当我在 Powershell 中启动它时,我必须多次(至少 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 的问题,或者是否有更优雅的方式来执行此输入?

4

3 回答 3

1

面对类似的要求,并且在 JLine 3 或 Spring-Shell 2 文档中的任何地方都没有找到这样的例子,我做了一些实验......

基本上——这些组件并非设计为以这种方式工作。在 @ShellMethod 的上下文中尝试获取额外的用户输入根本不是受支持的功能。

您可能使用的任何 IO 活动System.outSystem.in最终由 JLine “在后台”以不同方式处理,具体取决于您首先从哪种类型的 shell 开始(因此您观察到,当从 PowerShell 调用应用程序与启动应用程序时,您的尝试行为不同从 IDE 中)。如果我从 Windows 上的 GitBash 启动,我能够获得所需的行为,但如果我从 CMD 启动则不行。在一种情况下,我最终导致应用程序完全崩溃。

但是,我确实找到了一种提示输入的方法,该方法适用于 GitBash 和 CMD。这个例子在普通的 JLine 中不起作用,但在 Spring-Shell 2.0 中似乎可以工作,只要您使用 Spring Boot 自动配置(这使您可以访问 Spring 为您的 shell 应用程序初始化的相同的“LineReader”实例)。

@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);
}
于 2018-06-20T18:18:58.790 回答
1

我为此使用TextIO来做类似的事情。更具体地说,根据其文档,我使用了方便的read方法:[IntInputReader][2]

读取 T 类型的值。它反复提示用户输入该值,直到他们提供有效的输入字符串。

由于我在 Spring Shell 应用程序中多次需要此功能(供用户从不同类型的对象中进行选择),因此我编写了一个通用方法

向用户呈现给定类型 {@code T} 的项目列表,并提示他们选择一个或多个。可选地,一旦选择完成,将向用户呈现他们的选择摘要以供他们确认。

不幸的是,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);
  }
于 2019-04-15T14:09:16.410 回答
1

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 + "\"";
    }
}
于 2019-12-03T14:23:20.900 回答