Last active
February 3, 2023 14:04
-
-
Save mmpataki/8514550e3b8aa97f3e0cd98011e4e553 to your computer and use it in GitHub Desktop.
Cli arg parser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Exposed { | |
String value(); | |
} | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Argument { | |
String[] keys(); | |
String help(); | |
boolean required() default false; | |
boolean multivalued() default false; | |
boolean sensitive() default false; | |
String parser() default "defaultParser"; | |
} | |
public class Configuration { | |
@Argument(keys = {"-h", "--help"}, help = "Prints help") | |
private boolean help; | |
// input control | |
@Argument(keys = {"-i", "--input"}, help = "Input file (Can have SQL or shell commands)") | |
private String inputFile; | |
@Argument(keys = {"--props"}, help = "Config props file") | |
private String propsFile; | |
@Argument(keys = {"--printProps"}, help = "Prints sample props file") | |
private boolean printProps = false; | |
// internal for debugging | |
boolean __debug = false; | |
@Override | |
public String toString() { | |
return Arrays.stream(getClass().getDeclaredFields()).filter(f -> __debug || f.isAnnotationPresent(Exposed.class)).map(f -> { | |
try { | |
return String.format("%s = %s", f.getName(), f.get(this)); | |
} catch (IllegalAccessException e) { | |
return ""; | |
} | |
}).collect(Collectors.joining("\n")); | |
} | |
@SuppressWarnings("unchecked") | |
public void txParser(Field f, Configuration c, String s) throws Exception { | |
if (f.get(c) == null) f.set(c, new HashMap<>()); | |
if (s == null || s.isEmpty()) return; | |
((Map) f.get(c)).put(s.substring(0, s.indexOf("=")), (Transformer) Class.forName(s.substring(s.indexOf('=') + 1)).newInstance()); | |
} | |
public void defaultParser(Field f, Configuration c, String s) throws Exception { | |
Object val = null; | |
if (Integer.TYPE.isAssignableFrom(f.getType())) { | |
val = Integer.parseInt(s); | |
} else if (Long.TYPE.isAssignableFrom(f.getType())) { | |
val = Long.parseLong(s); | |
} else if (Double.TYPE.isAssignableFrom(f.getType())) { | |
val = Double.parseDouble(s); | |
} else if (Float.TYPE.isAssignableFrom(f.getType())) { | |
val = Float.parseFloat(s); | |
} else if (Boolean.TYPE.isAssignableFrom(f.getType())) { | |
val = s == null || Boolean.valueOf(s); | |
} else { | |
val = s.isEmpty() ? null : s; | |
} | |
f.set(c, val); | |
} | |
private void set(Field f, String val) throws Exception { | |
getClass().getMethod(f.getAnnotation(Argument.class).parser(), Field.class, Configuration.class, String.class) | |
.invoke(this, f, this, val); | |
} | |
public Configuration(String args[]) throws Exception { | |
Map<String, Field> fields = new LinkedHashMap<>(); | |
Arrays.stream(Configuration.class.getDeclaredFields()).filter(f -> f.isAnnotationPresent(Argument.class)).forEach(f -> { | |
Argument arg = f.getAnnotation(Argument.class); | |
for (String key : arg.keys()) | |
fields.put(key, f); | |
}); | |
for (int i = 0; i < args.length; i++) { | |
String arg = args[i]; | |
if (!fields.containsKey(arg)) { | |
log("unknown argument: " + arg); | |
System.exit(0); | |
} | |
Field field = fields.get(arg); | |
set(field, field.getType().isAssignableFrom(boolean.class) ? "true" : args[++i]); | |
} | |
if (propsFile != null) { | |
Properties props = new Properties(); | |
props.load(new FileReader(propsFile)); | |
for (Map.Entry<Object, Object> entry : props.entrySet()) { | |
set(getClass().getDeclaredField(entry.getKey().toString()), entry.getValue().toString()); | |
} | |
} | |
if (printProps) { | |
for (Field f : Configuration.class.getDeclaredFields()) { | |
if (f.isAnnotationPresent(Argument.class)) { | |
Argument arg = f.getAnnotation(Argument.class); | |
System.out.printf("# %s\n", arg.required() ? "REQUIRED" : "OPTIONAL"); | |
System.out.printf("# %s\n", f.getAnnotation(Argument.class).help()); | |
boolean noVal = f.get(this) == null || Map.class.isAssignableFrom(f.getType()) || Collection.class.isAssignableFrom(f.getType()); | |
System.out.printf("%s=%s\n", f.getName(), noVal ? "" : f.get(this)); | |
System.out.println(); | |
} | |
} | |
System.exit(0); | |
} | |
if (!help) { | |
for (Field f : fields.values()) { | |
Argument argConf = f.getAnnotation(Argument.class); | |
if (!(f.getAnnotation(Argument.class).required() && f.get(this) == null)) continue; | |
System.out.printf("Enter %s (%s): ", f.getName(), argConf.help()); | |
set(f, (argConf.sensitive()) ? new String(System.console().readPassword()) : sc.readLine()); | |
} | |
} | |
} | |
public String getHelp() { | |
StringBuilder sb = new StringBuilder(); | |
sb.append(String.format(" %-35s %4s %8s %s\n", "switch", "reqd", "multiple", "help")); | |
Arrays.stream(getClass().getDeclaredFields()).filter(f -> f.isAnnotationPresent(Argument.class)).forEach(f -> { | |
Argument arg = f.getAnnotation(Argument.class); | |
sb.append(String.format( | |
" %-35s %3s %4s %s\n", | |
String.join(", ", arg.keys()) + (f.getType().isAssignableFrom(boolean.class) ? "" : String.format(" <%s>", f.getName())), | |
arg.required() ? "*" : " ", | |
arg.multivalued() ? "*" : " ", | |
arg.help() | |
)); | |
}); | |
return sb.toString(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Driver { | |
public static void main(String args[]) throws Exception { | |
Configuration conf = new Configuration(args); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment