4

I'm currently trying to improve a Spring Shell application, and one of thing that would make it considerably better would be if it would support tab-completion for values as well as the options.

As an example, if my CLI had a command getInfo --name <name>, where <name> is a name from a finite set of names in the DB, it would be handy to be able to do

> getInfo --name Lu<tab>
  Lucy Luke Lulu
> getInfo --name Luk<tab>
> getInfo --name Luke

Is there any way to do this using the existing Spring Shell tooling? I've had a poke around in the documentation and can't find anything about autocompleting values..

Cheers!

Tetigi
  • 542
  • 1
  • 5
  • 20

2 Answers2

4

For Spring Shell 2, you need to implement the ValueProvider interface.

import org.springframework.shell.standard.ValueProvider;

@Component
public class MyValueProvider implements ValueProvider {

    @Override
    public boolean supports(MethodParameter methodParameter, CompletionContext completionContext) {
        return true;
    }

    @Override
    public List<CompletionProposal> complete(MethodParameter methodParameter, CompletionContext completionContext, String[] strings) {

        List<CompletionProposal> result = new ArrayList();
        List<String> knownThings = new ArrayList<>();
        knownPaths.add("something");

        String userInput = completionContext.currentWordUpToCursor();
        knownThings.stream()
                .filter(t -> t.startsWith(userInput))
                .forEach(t -> result.add(new CompletionProposal(t)));

        return result;
    }
}
Janux
  • 358
  • 3
  • 12
  • One could also extend `ValueProviderSupport` and us its already implemented `supports` method, so one could "activate" the `MyValueProvider` with `@ShellOption(valueProvider = MyValueProvider.class)` – Ralph Dec 03 '19 at 11:19
  • @Ralph, as you suggested I implemented a subclass of ValueProviderSupport that provides completion for File names and am setting the ShellOption valueProvider, but the default FilleValueProvider that I previously used still gets called instead of my explanation. Any idea why this would happen? – Chris94 Dec 21 '20 at 19:40
  • 1
    @Chris94: I have no clue, maybe one hint: it is required that the extended ValueProviderSupport class is initialized as Spring Bean! -- If your problem is ongoing, then I recomend to rise a new question and post your code – Ralph Jan 07 '21 at 12:30
  • Thanks--the missing bean annotation was the issue. – Chris94 Jan 09 '21 at 14:33
1

You can register a Converter to get this functionality to the String type. There are already a few already created converters and the org.springframework.shell.converters.EnumConverter has the functionality you are looking for.

To register a Converter you need to implement the org.springframework.shell.core.Converter interface:

@Component
public class PropertyConverter implements Converter<String> {

    public boolean supports(Class<?> type, String optionContext) {
        return String.class.isAssignableFrom(type)
                && optionContext != null && optionContext.contains("enable-prop-converter");
    }

    public String convertFromText(String value, Class<?> targetType, String optionContext) {
        return value;
    }

    public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData,
            String optionContext, MethodTarget target) {
        boolean result = false;

        if (String.class.isAssignableFrom(targetType)) {
            String [] values = {"ONE", "BOOKING", "BOOK"};

            for (String candidate : values) {
                if ("".equals(existingData) || candidate.startsWith(existingData) || existingData.startsWith(candidate)
                        || candidate.toUpperCase().startsWith(existingData.toUpperCase())
                        || existingData.toUpperCase().startsWith(candidate.toUpperCase())) {
                    completions.add(new Completion(candidate));
                }
            }

            result = true;
        }
        return result;
    }

}

In the previous example I've registered a converter for the String Object that it enables when the optionContext of the @CliOption Annotation has enable-prop-converter.

You also have to disable the default StringConverter. In the next line I disabled the default String converter in the @CliOption Annotation with disable-string-converter and enabled the new PropertyConverter with enable-prop-converter.

@CliOption(key = { "idProp" }, mandatory = true, help = "The the id of the property", optionContext="disable-string-converter,enable-prop-converter") final String idProp