遇到一个比较小的命令行解析需求,特意写了个较小的lib.
Options
用于定义参数的类,包含
Option
子类描述一个参数
代码如下:
package top.evalexp.ami.cmd;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
public class Options {
// shortNameMap: map shortname to Option object
private Map<String, Option> shortNameMap = new HashMap<>();
// longNameMap: map longname to Option object
private Map<String, Option> longNameMap = new HashMap<>();
/**
* Options.Type
* Specify the Option's Type
*/
public interface Type {
Object BOOLEAN = Boolean.FALSE;
Object INTEGER = 0;
Object DOUBLE = Double.NaN;
Object STRING = "";
}
static {
// add common caster
Parser.addCaster(Type.BOOLEAN.getClass(), Boolean::parseBoolean);
Parser.addCaster(Type.INTEGER.getClass(), Integer::parseInt);
Parser.addCaster(Type.DOUBLE.getClass(), Double::parseDouble);
Parser.addCaster(Type.STRING.getClass(), String::valueOf);
}
public void forEach(Consumer<Option> consumer) {
for (Map.Entry<String, Option> entry : longNameMap.entrySet()) {
consumer.accept(entry.getValue());
}
}
public Options() {
}
/**
* add an option
* @param option Option object
* @return this object, for chain call
* @param <T> Option Type
*/
public <T> Options addOption(Option<T> option) {
if (option.getName() == null || option.getName().isEmpty()) {
throw new IllegalArgumentException("option name or type is null or empty");
}
if (shortNameMap.containsKey(option.getShortName()) || longNameMap.containsKey(option.getName())) {
throw new IllegalArgumentException("option shortName or longName is already exist");
}
if (option.getShortName() != null) shortNameMap.put(option.getShortName(), option);
longNameMap.put(option.getName(), option);
return this;
}
/**
* add an option by name, shortname
* @param name argument name
* @param shortName argument shortname
* @param type argument type, only for type use, value is ignored
* @return this object, for chain call
* @param <T> Option type
*/
public <T> Options addOption(String name, String shortName, T type) {
return this.addOption(new Option<T>(name, shortName, null, null, type));
}
/**
* add an option by name, shortname
* @param name argument name
* @param shortName argument shortname
* @param defaultValue argument default value
* @param type argument type, only for type use, value is ignored
* @return this object, for chain call
* @param <T> Option type
*/
public <T> Options addOption(String name, String shortName, T type, T defaultValue) {
return this.addOption(new Option<T>(name, shortName, defaultValue, null, type));
}
/**
* add an option by name
* @param name argument name
* @param type argument type, only for type use, value is ignored
* @return this object, for chain call
* @param <T> Option type
*/
public <T> Options addOption(String name, T type) {
return this.addOption(new Option<T>(name, null, null, null, type));
}
/**
* add an option by name
* @param name argument name
* @param defaultValue argument default value
* @param type argument type, only for type use, value is ignored
* @return this object, for chain call
* @param <T> Option type
*/
public <T> Options addOption(String name, T type, T defaultValue) {
return this.addOption(new Option<T>(name, null, defaultValue, null, type));
}
/**
* add an option by name, checker
* @param name argument name
* @param checker argument checker
* @param type argument type, only for type use, value is ignored
* @return this object, for chain call
* @param <T> Option type
*/
public <T> Options addOption(String name, Function<T, Boolean> checker, T type) {
return this.addOption(new Option<T>(name, null, null, checker, type));
}
/**
* add an option by name, checker
* @param name argument name
* @param defaultValue argument default value
* @param checker argument checker
* @param type argument type, only for type use, value is ignored
* @return this object, for chain call
* @param <T> Option type
*/
public <T> Options addOption(String name, Function<T, Boolean> checker, T type, T defaultValue) {
return this.addOption(new Option<T>(name, null, defaultValue, checker, type));
}
/**
* add an option by name, shortname, checker
* @param name argument name
* @param shortName argument shortname
* @param checker argument checker
* @param type argument type, only for type use, value is ignored
* @return this object, for chain call
* @param <T> Option type
*/
public <T> Options addOption(String name, String shortName, Function<T, Boolean> checker, T type) {
return this.addOption(new Option<T>(name, shortName, null, checker, type));
}
/**
* add an option by name, shortname, checker
* @param name argument name
* @param shortName argument shortname
* @param defaultValue argument default value
* @param checker argument checker
* @param type argument type, only for type use, value is ignored
* @return this object, for chain call
* @param <T> Option type
*/
public <T> Options addOption(String name, String shortName, Function<T, Boolean> checker, T type, T defaultValue) {
return this.addOption(new Option<T>(name, shortName, defaultValue, checker, type));
}
/**
* get an option by its shortname
* @param shortName argument shortname
* @return Option
*/
public Option getOptionByShortName(String shortName) {
return shortNameMap.get(shortName);
}
/**
* get an option byte its long name
* @param longName argument long name
* @return Option
*/
public Option getOptionByLongName(String longName) {
return longNameMap.get(longName);
}
/**
* Argument Option class
* @param <T> Option Type
*/
public static class Option<T> {
private String name;
private String shortName;
private T type;
private Function<T, Boolean> checker;
private T defaultValue;
public Option(String name, String shortName, T defaultValue, Function<T, Boolean> checker, T type) {
this.name = name;
this.shortName = shortName;
this.defaultValue = defaultValue;
this.checker = checker;
this.type = type;
}
public String getName() {
return name;
}
public String getShortName() {
return shortName;
}
public boolean check(T value) {
return this.checker == null || checker.apply(value);
}
public T getType() {
return type;
}
public T getDefaultValue() {
return defaultValue;
}
}
}
Parser
用于实际解析的类
代码如下:
package top.evalexp.ami.cmd;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
public class Parser {
String[] args;
Options options;
// caster map
private static Map<Class, Function<String, Object>> cast = new HashMap<>();
/**
* add a caster
* @param type caster target type
* @param caster caster object, a function
*/
public static void addCaster(Class<?> type, Function<String, Object> caster) {
cast.put(type, caster);
}
/**
* Initializes a newly created Parser object without any pre-settings
*/
public Parser() {}
/**
* Initializes a newly created Parser object
* @param args program args
*/
public Parser(String[] args) {
this(args, null);
}
/**
* Initializes a newly created Parser object
* @param options commandline argument options
*/
public Parser(Options options) {
this(null, options);
}
/**
* Initializes a newly created Parser object
* @param args program args
* @param options commandline argument options
*/
public Parser(String[] args, Options options) {
this.args = args;
this.options = options;
}
/**
* set the commandline argument options
* @param options commandline argument options
* @return this object, for chain call
*/
public Parser setOptions(Options options) {
this.options = options;
return this;
}
/**
* set the program arguments
* @param args program args
* @return this object, for chain call
*/
public Parser setArgs(String[] args) {
this.args = args;
return this;
}
/**
* parse the program argument
* @return a newly Argument object
*/
public Argument parse() {
Argument argument = new Argument();
// set default value
this.options.forEach(option -> {
if (option.getDefaultValue() != null) {
if (!option.check(option.getDefaultValue())) {
System.err.printf("Warning: \"%s\" argument with default value \"%s\" is illegal%n", option.getName(), option.getDefaultValue());
return ;
}
argument.put(option.getName(), option.getDefaultValue());
}
});
for (int i = 0; i < this.args.length; i++) {
String arg = args[i];
if (arg.startsWith("-")) {
Options.Option option = null;
if (arg.startsWith("--")) {
option = this.options.getOptionByLongName(arg.substring(2));
} else if (arg.startsWith("-")) {
option = this.options.getOptionByShortName(arg.substring(1));
}
if (option == null) continue;
String value = null;
if (!(option.getType() instanceof Boolean)) {
value = (i + 1 < args.length && !args[i + 1].startsWith("-")) ? args[++i] : null;
} else {
argument.put(option.getName(), true);
continue;
}
this.putValue(argument, option, value);
}
}
return argument;
}
private void putValue(Argument argument, Options.Option option, String value) {
if (value != null && !value.isEmpty() && Parser.cast.containsKey(option.getType().getClass())) {
Object castValue = Parser.cast.get(option.getType().getClass()).apply(value);
if (!option.check(castValue)) {
System.err.printf("Warning: \"%s\" argument with value \"%s\" is illegal%n", option.getName(), castValue);
return ;
}
argument.put(option.getName(), castValue);
}
}
}
Argument
用于简化参数操作的最终参数解析结果类
代码如下:
package top.evalexp.ami.cmd;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Argument {
private final Map<String, Object> arguments = new HashMap<>();
private final List<Binding<?>> bindings = new ArrayList<>();
/**
* Binding bind argument value
* @param <T> Value type
*/
public static class Binding<T> {
private T value;
private String name;
public Binding(String name) {
this.name = name;
}
public T get() {
return value;
}
}
/**
* BindingContext the bind variable context
*/
public static class BindingContext {
private final List<Binding<?>> bindings = new ArrayList<>();
private Argument parent;
private BindingContext(Argument parent) {
this.parent = parent;
}
/**
* set binding
* @param binding binding
* @return context, chain call
*/
public BindingContext withBinding(Binding<?> binding) {
bindings.add(binding);
return this;
}
/**
* set all argument to binding value
*/
public void bind() {
for (Binding binding : bindings) {
binding.value = parent.get(binding.name);
}
}
}
/**
* get an argument value
* @param key argument name
* @return argument value
*/
public Object get(String key) {
return arguments.getOrDefault(key, null);
}
/**
* put an argument value
* @param key argument name
* @param value argument value
*/
protected void put(String key, Object value) {
arguments.put(key, value);
}
public BindingContext newBindingContext() {
return new BindingContext(this);
}
/**
* Stringify the object, contains all argument value
* @return String
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Argument{");
for (Map.Entry<String, Object> entry : arguments.entrySet()) {
builder.append('"').append(entry.getKey()).append('"').append(": ");
Object value = entry.getValue();
if (value instanceof String) {
builder.append("\"").append(value).append("\", ");
} else {
builder.append(value).append(", ");
}
}
if (builder.length() > 9) {
builder.delete(builder.length() - 2, builder.length());
}
builder.append("}");
return builder.toString();
}
}