select from a small amount of options in spring shell

后端 未结 3 941
感情败类
感情败类 2021-01-23 15:53

I am currently trying to make an cli app with Spring Shell.

I want the user to able to fast pick one of 2-3 options. My current code works fine in eclipse but when I sta

相关标签:
3条回答
  • 2021-01-23 16:12

    Facing a similar requirement, and not finding an example of such a thing anywhere in the JLine 3 or Spring-Shell 2 docs, I did some experimenting...

    Basically -- these components are not designed to work that way. Trying to get additional user input while already in the context of a @ShellMethod is simply not a supported feature.

    Any IO activity that you might do using System.out or System.in ends up being handled differently "under the hood" by JLine depending on what type of shell you first started from (hence your observation that your attempt behaves differently when the app is invoked from PowerShell vs being launched from within the IDE). I was able to get the desired behavior if I launched from GitBash on Windows, but not if I launched from CMD. In one case I just ended up crashing the application completely.

    However, I did find one way to prompt for input that worked for both GitBash and CMD. This example will not work in plain JLine, but appears to work in Spring-Shell 2.0 as long as you are using Spring Boot autoconfiguration (which gives you access to the same 'LineReader' instance that Spring initialized for your shell application).

    @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);
    }
    
    0 讨论(0)
  • 2021-01-23 16:13

    Spring Shell 2 is based on JLine 3. For just a "small amount of options" I would recommend to use a feature like tab completion.

    (It works on Powershell, but I never get any tab-completion support in Eclipse-Console).

    It is quite easy:

    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 + "\"";
        }
    }
    
    0 讨论(0)
  • 2021-01-23 16:15

    I use TextIO for this to do something similar. More specifically, I use the read method on [IntInputReader][2] which is handy as according to its docs:

    Reads a value of type T. It repeatedly prompts the users to enter the value, until they provide a valid input string.

    As I've needed this functionality many times in my Spring Shell application (for users to select from different types of objects), I wrote a generic method which

    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.

    Unfortunately StackOverflow is playing havoc with my Javadoc, so I've created a 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();
    }
    }
    

    Sample usage:

      @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);
      }
    
    0 讨论(0)
提交回复
热议问题