Skip to content

Instantly share code, notes, and snippets.

@quat1024
Last active June 2, 2024 17:55
Show Gist options
  • Save quat1024/d883ef6d76eb8dce18678fdd725e0585 to your computer and use it in GitHub Desktop.
Save quat1024/d883ef6d76eb8dce18678fdd725e0585 to your computer and use it in GitHub Desktop.
Good-enough command line argument parsing in about a page of Java
// This is kind of shaped like lexopt: https://github.com/blyxxyz/lexopt
sealed interface Opt permits ShortOpt, LongOpt, MiscOpt {
default boolean isShort(char c) { return this instanceof ShortOpt o && o.opt == c; }
default boolean isLong(String l) { return this instanceof LongOpt o && o.opt.equals(l); }
}
record ShortOpt(char opt) implements Opt {} //the "a", "b", or "c" in "-abc"
record LongOpt(String opt) implements Opt {} //the "option" in "--option"
record MiscOpt(String etc) implements Opt {} //something without a hyphen prefix
class OptLexer implements Iterator<Opt>, Iterable<Opt> {
public OptLexer(String[] args) { this.args = args; }
private final String[] args;
enum Mode {READY, SHORT}
private Mode mode = Mode.READY;
private int idx = 0, subitem = 0;
@Override
public boolean hasNext() {
return idx < args.length;
}
@Override
public Opt next() {
String currentArg = args[idx];
return switch(mode) {
case READY -> {
if(currentArg.startsWith("--")) {
idx++;
yield new LongOpt(currentArg.substring(2));
} else if(currentArg.startsWith("-")) {
mode = Mode.SHORT;
subitem = 1;
yield next();
} else {
idx++;
yield new MiscOpt(currentArg);
}
}
case SHORT -> {
ShortOpt shortOpt = new ShortOpt(currentArg.charAt(subitem));
subitem++;
if(subitem == currentArg.length()) {
mode = Mode.READY;
idx++;
}
yield shortOpt;
}
};
}
public String value() {
return switch(mode) {
case SHORT -> {
String value = args[idx].substring(subitem);
mode = Mode.READY;
idx++;
yield value.isEmpty() ? null : value;
}
case READY -> hasNext() ? args[idx++] : null;
};
}
@Override
public Iterator<Opt> iterator() {
return this;
}
}
// Java 8 compatible
interface Opt {
default boolean isShort(char c) { return this instanceof ShortOpt && ((ShortOpt) this).opt == c; }
default boolean isLong(String l) { return this instanceof LongOpt && ((LongOpt) this).opt.equals(l); }
}
class ShortOpt implements Opt {
public ShortOpt(char opt) { this.opt = opt; }
public char opt;
@Override public String toString() { return "ShortOpt " + opt; }
}
class LongOpt implements Opt {
public LongOpt(String opt) { this.opt = opt; }
public String opt;
@Override public String toString() { return "LongOpt " + opt; }
}
class MiscOpt implements Opt {
public MiscOpt(String etc) { this.etc = etc; }
public String etc;
@Override public String toString() { return "MiscOpt " + opt; }
}
class OptLexer implements Iterator<Opt>, Iterable<Opt> {
public OptLexer(String[] args) { this.args = args; }
private final String[] args;
enum Mode {READY, SHORT}
private Mode mode = Mode.READY;
private int idx = 0, subitem = 0;
@Override
public boolean hasNext() {
return idx < args.length;
}
@Override
public Opt next() {
while(true) {
String currentArg = args[idx];
switch(mode) {
case READY:
if(currentArg.startsWith("--")) {
idx++;
return new LongOpt(currentArg.substring(2));
} else if(currentArg.startsWith("-")) {
mode = Mode.SHORT;
subitem = 1;
continue;
} else {
idx++;
return new MiscOpt(currentArg);
}
case SHORT:
ShortOpt shortOpt = new ShortOpt(currentArg.charAt(subitem));
subitem++;
if(subitem == currentArg.length()) {
mode = Mode.READY;
idx++;
}
return shortOpt;
}
}
}
public String value() {
if(mode == Mode.SHORT) {
String value = args[idx].substring(subitem);
mode = Mode.READY;
idx++;
return value.isEmpty() ? null : value;
} else {
return hasNext() ? args[idx++] : null;
}
}
@Override
public Iterator<Opt> iterator() {
return this;
}
}
public static void main(String[] args) {
OptLexer lexer = new OptLexer(args);
for(Opt opt : lexer) {
if(opt.isShort('h') || opt.isLong("help")) // -h, --help
System.out.println("Usage: (...)");
else if(opt.isLong("key")) // --key value
System.out.println("value: " + lexer.value());
else if(opt.isShort('O')) // -Ofast
System.out.println(lexer.value());
else System.out.println("unrecognized option " + opt);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment