Last active
November 29, 2025 05:54
-
-
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
| import java.io.FileReader; | |
| import java.lang.annotation.Retention; | |
| import java.lang.annotation.RetentionPolicy; | |
| import java.lang.reflect.Field; | |
| import java.util.*; | |
| import java.util.stream.Collectors; | |
| public class Configuration { | |
| @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"; | |
| } | |
| @Retention(RetentionPolicy.RUNTIME) | |
| public @interface Exposed { | |
| String value(); | |
| } | |
| @Argument(keys = {"-h", "--help"}, help = "Prints help") | |
| public boolean help; | |
| @Argument(keys = {"--props"}, help = "Config props file") | |
| public String propsFile; | |
| @Argument(keys = {"--printProps"}, help = "Prints sample props file") | |
| public boolean printProps = false; | |
| // internal for debugging | |
| boolean __debug = false; | |
| @Override | |
| public String toString() { | |
| return getFields().stream().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")); | |
| } | |
| 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; | |
| } | |
| boolean acc = f.canAccess(this); | |
| f.setAccessible(true); | |
| f.set(c, val); | |
| f.setAccessible(acc); | |
| } | |
| 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 List<Field> getFields() { | |
| List<Field> fields = new LinkedList<>(); | |
| Class clz = getClass(); | |
| while (clz != Object.class) { | |
| for (Field declaredField : clz.getDeclaredFields()) { | |
| if (declaredField.isAnnotationPresent(Argument.class)) | |
| fields.add(declaredField); | |
| } | |
| clz = clz.getSuperclass(); | |
| } | |
| return fields; | |
| } | |
| public void parse(String args[]) throws Exception { | |
| Map<String, Field> fields = new LinkedHashMap<>(); | |
| getFields().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)) { | |
| System.out.println("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().getField(entry.getKey().toString()), entry.getValue().toString()); | |
| } | |
| } | |
| if (printProps) { | |
| for (Field f : getFields()) { | |
| 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()) : System.console().readLine()); | |
| } | |
| } | |
| } | |
| public String getHelp() { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.append(String.format(" %-35s %4s %8s %s\n", "switch", "reqd", "multiple", "help")); | |
| getFields().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 { | |
| static class Config extends Configuration { | |
| @Argument(keys = {"-i", "--input"}, help = "Input file") | |
| private String inputFile = "./text8"; | |
| @Argument(keys = {"-e", "--epochs"}, help = "Number of epochs to train on") | |
| private int epochs = 5; | |
| @Argument(keys = {"-t", "--threads"}, help = "Number of threads") | |
| private int threads = Runtime.getRuntime().availableProcessors(); | |
| @Argument(keys = {"-v", "--vector-size"}, help = "Embedding vector dimension") | |
| private int emb_size = 128; | |
| @Argument(keys = {"-w", "--window-size"}, help = "Window size") | |
| private int windowSize = 10; | |
| @Argument(keys = {"-ns", "--negative-samples"}, help = "number of negative samples per word pair") | |
| private int negativeSamples = 10; | |
| @Argument(keys = {"-a", "--alpha"}, help = "initial alpha (learning rate)") | |
| private float alpha = 0.005F; | |
| @Argument(keys = {"--test"}, help = "Run in testing mode") | |
| private boolean testMode = false; | |
| @Argument(keys = {"--local-rng"}, help = "Use local random number generator") | |
| private boolean localRng = false; | |
| public Config(String[] args) throws Exception { | |
| parse(args); | |
| } | |
| } | |
| public static void main(String args[]) throws Exception { | |
| Config conf = new Config(args); | |
| if(c.help) { | |
| System.out.println(c.getHelp()); | |
| System.exit(0); | |
| } | |
| // ... | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment