Picocli is a modern framework for building powerful, user-friendly, GraalVM-enabled command line apps with ease. It supports colors, autocompletion, subcommands, and more. In 1 source file so apps can include as source & avoid adding a dependency. Written in Java, usable from Groovy, Kotlin, Scala, etc.
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()));
}
}
}
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);
}
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);
}
}
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 assingle-line
. Obviously, we can't use-
when defining enum values, so for this example we define an enum valuesingle_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 theOption
definition, for example: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:
Implementations of these interfaces could then be registered on
CommandLine
orCommandSpec
. Then, whenever picocli needs to convert an input string to store it into an option field, and the option doesn't explicitly specify aconverter
, picocli would iterate over the registeredIGenericTypeConverter
instances (either in reverse order of addition, or by allowing explicit ordering) to find the first one for whichcanConvert
returns true, then calling theconvert
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 defineconverter
andcompletionCandidates
on every enum option: