Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic converter/completion candidates iterator for enum values? #2025

Open
rsenden opened this issue May 15, 2023 · 1 comment
Open

Generic converter/completion candidates iterator for enum values? #2025

rsenden opened this issue May 15, 2023 · 1 comment

Comments

@rsenden
Copy link
Contributor

rsenden commented May 15, 2023

Our application provides various CLI options taking enum values. Some of these enums have values consisting of multiple words like single line, which we'd like users to enter as single-line. Obviously, we can't use - when defining enum values, so for this example we define an enum value single_line and use a custom converter and completion candidates iterator to convert between _ and -. Currently we need to create a custom converter and iterator class for every enum, and explicitly specify these in the Option definition, for example:

public final class ProgressWriterTypeConverter implements ITypeConverter<ProgressWriterType> {
    @Override
    public ProgressWriterType convert(String value) throws Exception {
        return ProgressWriterType.valueOf(value.replace('-', '_'));
    }
    
    public static final class ProgressWriterTypeIterable extends ArrayList<String> {
        private static final long serialVersionUID = 1L;
        public ProgressWriterTypeIterable() { 
            super(Stream.of(ProgressWriterType.values())
                    .map(Enum::name)
                    .map(s->s.replace('_', '-'))
                    .collect(Collectors.toList())); 
        }
    }
}
@Option(names="--progress", defaultValue = "auto", completionCandidates = ProgressWriterTypeIterable.class, converter = ProgressWriterTypeConverter.class ) 
    private ProgressWriterType type;

Also, as a best practice, enum values should be uppercase, and although picocli optionally allows for case-insensitive matching, we'd like to have the help output display completion candidates in lowercase; again we'd need to define a custom completion candidates iterator on every option that takes an enum value. For now, we avoid this by just using lowercase enum values.

I think it would be nice if picocli allowed for registering one or more generic custom type converters and completion candidate suppliers for arbitrary types (which could be enums or other custom types), based on interfaces like the following:

public interface IGenericTypeConverter {
    boolean canConvert(Class<?> optionFieldType);
    <T> T convert(String value, Class<T> optionFieldType);
}
public interface IGenericCompletionCandidatesSupplier {
    boolean canGenerateCompletionCandidates(Class<?> optionFieldType);
    Iterator/Collection/... getCompletionCandidates(Class<?> optionFieldType);
}

Implementations of these interfaces could then be registered on CommandLine or CommandSpec. Then, whenever picocli needs to convert an input string to store it into an option field, and the option doesn't explicitly specify a converter, picocli would iterate over the registered IGenericTypeConverter instances (either in reverse order of addition, or by allowing explicit ordering) to find the first one for which canConvert returns true, then calling the convert method to get the converted value. Obviously, same procedure would apply when generating completion candidates.

With this approach, we could register a GenericEnumTypeConverter like the following, handling both case-insensitivity and converting - to _, without having to explicitly define converter and completionCandidates on every enum option:

    public static class GenericEnumTypeConverter implements IGenericTypeConverter {
        @Override
        public boolean canConvert(Class<?> optionFieldType) {
            return Enum.class.isAssignableFrom(optionFieldType);
        }
        @Override
        public <T> T convert(String value, Class<T> optionFieldType) {
            return convertEnumValue(value.toUpperCase().replace('-', '_'), optionFieldType);
        }
        @SuppressWarnings({ "rawtypes", "unchecked" })
        private <T> T convertEnumValue(String value, Class type) {
            return (T)Enum.valueOf(type, value);
        }
    }
@remkop
Copy link
Owner

remkop commented May 16, 2023

Potentially related: #1804

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants