Last active
June 2, 2024 17:55
-
-
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 file contains 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
// 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; | |
} | |
} |
This file contains 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
// 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; | |
} | |
} |
This file contains 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
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