从弹簧外壳中选择少量选项

时间:2018-04-11 08:41:53

标签: java command-line-interface spring-shell

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

我希望用户能够快速选择2-3个选项中的一个。我目前的代码在eclipse中工作得很好但是当我在Powershell中启动它时,我必须多次输入(至少3次)才能选择该选项。按下输入一次后,没有任何反应。

我目前的方法:

@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的问题,还是有更优雅的方式来执行此输入?

3 个答案:

答案 0 :(得分:1)

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

基本上-这些组件并非旨在以这种方式工作。完全不支持在@ShellMethod上下文中尝试获取其他用户输入。

您可能使用System.outSystem.in进行的任何IO活动最终都会由JLine在“幕后”进行不同的处理,具体取决于您最初启动的外壳类型(因此观察到从PowerShell调用应用程序与从IDE内部启动应用程序时,尝试的行为会有所不同。如果从Windows上的GitBash启动,则能够获得所需的行为,但如果从CMD启动,则无法获得预期的行为。在一种情况下,我最终导致应用程序完全崩溃。

但是,我确实找到了一种提示输入的方法,该输入对GitBash和CMD都适用。该示例无法在普通的JLine中使用,但只要您使用的是Spring Boot自动配置(就可以使用Spring为您的Shell应用程序初始化的“ LineReader”实例),它就可以在Spring-Shell 2.0中使用。

@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);
}

答案 1 :(得分:1)

Spring Shell 2基于JLine 3。对于“少量选项”,我建议使用选项卡补全等功能。

enter image description here

(它可以在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 + "\"";
    }
}

答案 2 :(得分:0)

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

  

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

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

  

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

不幸的是,StackOverflow正在对我的Javadoc造成破坏,所以我创建了Gist with the full documented method

  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);
  }