package com.laytonsmith.PureUtilities;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* An ArgumentParser allows for programmatic registration of arguments,
* which will be automatically parsed and validated. Additionally,
* automatically generated help text can be retrieved and displayed, perhaps
* if a --help argument is present.
*
*/
public class ArgumentParser {
/**
* A description of the command itself.
*/
String description = "";
List<Argument> argumentModel = new ArrayList<Argument>();
/**
* Returns the default argument, if it exists.
*
* @return
*/
private Argument getArgument() {
for (Argument a : argumentModel) {
if (a.shortArg == null && a.longArg == null) {
return a;
}
}
return null;
}
private Argument getArgument(Character c) {
for (Argument a : argumentModel) {
if (a.shortArg == null) {
continue;
}
if (a.shortArg.equals(c)) {
return a;
}
}
return null;
}
private Argument getArgument(String s) {
for (Argument a : argumentModel) {
if (a.longArg == null) {
continue;
}
if (a.longArg.equals(s)) {
return a;
}
}
return null;
}
private class Argument {
Character shortArg;
String longArg;
Type argType;
String defaultVal;
List<String> defaultList;
String description;
String usageName;
boolean required;
String singleVal;
List<String> arrayVal;
private Argument(Argument arg) {
if (arg == null) {
return;
}
this.shortArg = arg.shortArg;
this.longArg = arg.longArg;
this.argType = arg.argType;
this.defaultVal = arg.defaultVal;
this.description = arg.description;
this.usageName = arg.usageName;
this.required = arg.required;
}
private Argument(Character shortArg, String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) {
this.shortArg = shortArg;
this.longArg = longArg;
this.argType = argType;
this.description = description;
this.defaultVal = defaultVal;
if (isArray() && defaultVal != null) {
defaultList = ArgumentParser.this.lex(defaultVal);
}
this.usageName = usageName;
this.required = required;
}
public final boolean isFlag() {
return argType == Type.BOOLEAN;
}
public final boolean isArray() {
return argType == Type.ARRAY_OF_NUMBERS || argType == Type.ARRAY_OF_STRINGS;
}
public final boolean isSingle() {
return argType == Type.NUMBER || argType == Type.STRING;
}
public final boolean isNumeric() {
return argType == Type.NUMBER || argType == Type.ARRAY_OF_NUMBERS;
}
private void setValue(String val) {
if (isArray()) {
arrayVal = ArgumentParser.this.lex(val);
} else {
singleVal = val;
}
}
private void setValue(List<String> val) {
arrayVal = new ArrayList<>(val);
}
public boolean modelEquals(Argument obj) {
if (this.shortArg != null) {
return this.shortArg.equals(obj.shortArg);
} else if (this.longArg != null) {
return this.longArg.equals(obj.longArg);
} else {
return obj.shortArg == null && obj.longArg == null;
}
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
if (longArg != null && shortArg != null) {
b.append("--").append(longArg).append("/").append("-").append(shortArg);
} else if (longArg != null) {
b.append("--").append(longArg);
} else if (shortArg != null) {
b.append("-").append(shortArg);
}
b.append(": ");
if (isSingle()) {
b.append(singleVal);
} else if (isArray()) {
boolean first = true;
b.append("[");
for (String s : arrayVal) {
if (!first) {
b.append(", ");
}
first = false;
b.append("\"").append(s.replaceAll("\"", "\\\"")).append("\"");
}
b.append("]");
}
b.append("\n");
return b.toString();
}
private String generateDescription(boolean shortCode) {
StringBuilder b = new StringBuilder();
b.append("\t");
if (shortArg == null && longArg == null) {
//Default argument
b.append("<").append(usageName).append(">: ").append(description).append("\n");
} else {
//If short code is false, we need to check to see if there is a short code, if so,
//this is an alias.
if (shortCode) {
b.append("-").append(shortArg);
} else {
b.append("--").append(longArg);
}
b.append(": ");
if (!shortCode && shortArg != null) {
//Alias
b.append("Alias to -").append(shortArg);
} else {
if (argType != Type.BOOLEAN) {
if (required) {
b.append("Required. ");
} else {
b.append("Optional. ");
}
}
if (argType == Type.NUMBER) {
b.append("A numeric value. ");
}
if (argType == Type.ARRAY_OF_NUMBERS) {
b.append("A list of numbers. ");
}
if (argType == Type.ARRAY_OF_STRINGS) {
b.append("A list. ");
}
b.append(description.replaceAll("\n", "\n\t\t"));
}
b.append("\n");
}
return b.toString();
}
}
private ArgumentParser() {
}
public static class ValidationException extends Exception {
private ValidationException(String string) {
super(string);
}
}
public static class ResultUseException extends RuntimeException {
ResultUseException(String string) {
super(string);
}
}
public class ArgumentParserResults {
List<Argument> arguments = new ArrayList<Argument>();
private void updateArgument(Argument a) {
if (a == null) {
return;
}
List<Argument> toRemove = new ArrayList<Argument>();
for (Argument arg : arguments) {
if (arg.modelEquals(a)) {
toRemove.add(arg);
}
}
for (Argument arg : toRemove) {
arguments.remove(arg);
}
arguments.add(a);
}
/**
* Returns true if the flag represented by this short code is set.
*
* @param flag
* @return
*/
public boolean isFlagSet(Character flag) {
return getArg(flag) != null;
}
/**
* Returns true is the flag represented by this long code is set.
*
* @param flag
* @return
*/
public boolean isFlagSet(String flag) {
return getArg(flag) != null;
}
/**
* Gets the unassociated arguments passed in as a String. For instance,
* if the arguments were
* <code>These are arguments</code>, then "These are arguments" will be
* returned. However, assuming -c is registered as a single string type,
* and the arguments are
* <code>-c These are arguments</code>, then only "are arguments" is
* returned. This will return an empty string if no arguments were set.
*
* @return
*/
public String getStringArgument() {
try {
Argument a = getArg();
if (a.arrayVal == null) {
return "";
}
StringBuilder b = new StringBuilder();
boolean first = true;
for (String val : a.arrayVal) {
if (!first) {
b.append(" ");
}
first = false;
b.append(val);
}
return b.toString();
}
catch (ResultUseException e) {
return "";
}
}
/**
* Returns the string associated with the switch represented by this
* short code. If the switch wasn't set, null is returned.
*
* @param flag
* @return
* @throws ArgumentParser.ResultUseException
*/
public String getStringArgument(Character flag) throws ResultUseException {
return getStringArgument(getArg(flag));
}
/**
* Returns the string associated with the switch represented by this
* long code. If the switch wasn't set, null is returned.
*
* @param flag
* @return
* @throws ArgumentParser.ResultUseException
*/
public String getStringArgument(String flag) throws ResultUseException {
return getStringArgument(getArg(flag));
}
private String getStringArgument(Argument arg) {
if (arg == null) {
return null;
}
if (arg.argType != Type.STRING) {
throw new ClassCastException("Argument type not set to " + Type.STRING.name() + ". Cannot return a " + "string" + ".");
}
return arg.singleVal;
}
/**
* Returns the value associated with the switch represented by this
* short code, pre-parsed as a double. If the switch wasn't set, null is
* returned.
*
* @param flag
* @return
* @throws ResultUseException, NumberFormatException
*/
public Double getNumberArgument(Character flag) throws ResultUseException {
return getNumberArgument(getArg(flag));
}
/**
* Returns the value associated with the switch represented by this long
* code, pre-parsed as a double. If the switch wasn't set, null is
* returned.
*
* @param flag
* @return
* @throws ResultUseException, NumberFormatException
*/
public Double getNumberArgument(String flag) throws ResultUseException {
return getNumberArgument(getArg(flag));
}
private Double getNumberArgument(Argument arg) {
if (arg == null) {
return null;
}
if (arg.argType != Type.NUMBER) {
throw new ClassCastException("Argument type not set to " + Type.NUMBER.name() + ". Cannot return a " + "number" + ".");
}
return Double.parseDouble(arg.singleVal);
}
/**
* Gets the unassociated arguments passed in as a List of Strings. For
* instance, if the arguments were
* <code>These are arguments</code>, then ["These", "are", "arguments"]
* will be returned. However, assuming -c is registered as a single
* string type, and the arguments are
* <code>-c These are arguments</code>, then only ["are", "arguments"]
* is returned. This will return an empty array if no arguments were
* set.
*
* @return
*/
public List<String> getStringListArgument() {
try {
Argument a = getArg();
if (a.arrayVal == null) {
return new ArrayList<String>();
}
return new ArrayList<String>(a.arrayVal);
}
catch (ResultUseException e) {
return new ArrayList<String>();
}
}
/**
* Returns the list of values associated with the switch represented by
* this short code. If the switch wasn't set, null is returned.
*
* @param flag
* @return
* @throws ArgumentParser.ResultUseException
*/
public List<String> getStringListArgument(Character flag) throws ResultUseException {
return getStringListArgument(getArg(flag));
}
/**
* Returns the list of values associated with the switch represented by
* this long code. If the switch wasn't set, null is returned.
*
* @param flag
* @return
* @throws ArgumentParser.ResultUseException
*/
public List<String> getStringListArgument(String flag) throws ResultUseException {
return getStringListArgument(getArg(flag));
}
private List<String> getStringListArgument(Argument arg) {
if (arg == null) {
return null;
}
if (arg.argType != Type.ARRAY_OF_STRINGS) {
throw new ClassCastException("Argument type not set to " + Type.ARRAY_OF_STRINGS.name() + ". Cannot return a " + "string list" + ".");
}
return new ArrayList<String>(arg.arrayVal);
}
/**
* Returns the list of values associated with the switch represented by
* this short code, pre-parsed into doubles. If the switch wasn't set,
* null is returned.
*
* @param flag
* @return
* @throws ArgumentParser.ResultUseException
*/
public List<Double> getNumberListArgument(Character flag) throws ResultUseException {
return getNumberListArgument(getArg(flag));
}
/**
* Returns the list of values associated with the switch represented by
* this long code, pre-parsed into doubles. If the switch wasn't set,
* null is returned.
*
* @param flag
* @return
* @throws ArgumentParser.ResultUseException
*/
public List<Double> getNumberListArgument(String flag) throws ResultUseException {
return getNumberListArgument(getArg(flag));
}
private List<Double> getNumberListArgument(Argument arg) {
if (arg == null) {
return null;
}
if (arg.argType != Type.ARRAY_OF_NUMBERS) {
throw new ClassCastException("Argument type not set to " + Type.ARRAY_OF_NUMBERS.name() + ". Cannot return a " + "number list" + ".");
}
List<Double> list = new ArrayList<Double>();
for (String s : arg.arrayVal) {
list.add(Double.parseDouble(s));
}
return list;
}
private Argument getArg() {
for (Argument a : arguments) {
if (a.shortArg == null && a.longArg == null) {
return a;
}
}
return new Argument(ArgumentParser.this.getArgument());
}
private Argument getArg(Character flag) throws ResultUseException {
for (Argument a : arguments) {
if (a.shortArg == null) {
continue;
}
if (a.shortArg.equals(flag)) {
return a;
}
}
return null;
}
private Argument getArg(String flag) throws ResultUseException {
for (Argument a : arguments) {
if (a.longArg == null) {
continue;
}
if (a.longArg.equals(flag)) {
return a;
}
}
return null;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
for (Argument arg : arguments) {
if (arg.isFlag()) {
b.append("Flag ");
if (arg.longArg != null && arg.shortArg != null) {
b.append("--").append(arg.longArg).append("/").append("-").append(arg.shortArg);
} else if (arg.longArg != null) {
b.append("--").append(arg.longArg);
} else if (arg.shortArg != null) {
b.append("-").append(arg.shortArg);
}
b.append(" is set.\n");
} else {
b.append(arg.toString());
}
}
return b.toString();
}
}
public static ArgumentParser GetParser() {
return new ArgumentParser();
}
public static enum Type {
STRING,
NUMBER,
ARRAY_OF_STRINGS,
ARRAY_OF_NUMBERS,
BOOLEAN
}
private ArgumentParser addArgument0(Character shortArg, String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) {
//TODO: Make sure this switch doesn't already exist
argumentModel.add(new Argument(shortArg, longArg, argType, defaultVal, description, usageName, required));
return this;
}
/**
* Adds an argument to this argument parser. This is the most complex method
* of adding an argument, all other methods are wrappers around this.
*
* The short code and long code for a switch both represent the underlying
* switch, that is, they both "addresses" of a single underlying switch.
* When accessing the argument later, you may use either the short code or
* the long code to retrieve the value of the switch, but it is important to
* understand that they are both pointing to the same item.
*
* @param shortArg The short code for this switch.
* @param longArg The long code for this switch.
* @param argType The expected type of this switch.
* @param defaultVal The default value of this switch. If defaultVal is not
* null, the switch will always exist when calling get*Argument from the
* results. If argType is BOOLEAN, setting this will cause the switch to
* @param description The description of this argument, which is used when
* building the help text created by getBuiltDescription.
* @return
*/
public ArgumentParser addArgument(Character shortArg, String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) {
if (argType == Type.BOOLEAN) {
throw new IllegalArgumentException("Cannot use addArgument to add a flag. Use addFlag instead.");
}
if (shortArg == null && longArg == null) {
if (argType != Type.STRING && argType != Type.ARRAY_OF_STRINGS) {
throw new IllegalArgumentException("Cannot set the type of the default switch to anything but " + Type.STRING.name() + " or "
+ Type.ARRAY_OF_STRINGS.name());
}
}
return addArgument0(shortArg, longArg, argType, defaultVal, description, usageName, required);
}
/**
* Sets the default switch's arg type, default value, and description. The
* default switch is the switch that is associated with "loose" arguments,
* for instance,
* <code>these are args</code> would all be loose arguments, because they
* aren't associated with any explicit switches. Note that there is no Type
* specified here, that's because the arguments can be grabbed as either an
* array of strings or a string.
*
* @param argType
* @param defaultVal
* @param description
* @return
*/
public ArgumentParser addArgument(String defaultVal, String description, String usageName, boolean required) {
return addArgument(null, null, Type.ARRAY_OF_STRINGS, defaultVal, description, usageName, required);
}
/**
* Sets the default switch with no default value.
*
* @param argType
* @param description
* @return
*/
public ArgumentParser addArgument(String description, String usageName, boolean required) {
return addArgument(null, null, Type.ARRAY_OF_STRINGS, null, description, usageName, required);
}
/**
* Adds a new argument with no default value.
*
* @param shortArg
* @param longArg
* @param argType
* @param description
* @return
*/
public ArgumentParser addArgument(Character shortArg, String longArg, Type argType, String description, String usageName, boolean required) {
return addArgument(shortArg, longArg, argType, null, description, usageName, required);
}
/**
* Adds a new argument with no long code.
*
* @param shortArg
* @param argType
* @param defaultVal
* @param description
* @return
*/
public ArgumentParser addArgument(Character shortArg, Type argType, String defaultVal, String description, String usageName, boolean required) {
return addArgument(shortArg, null, argType, defaultVal, description, usageName, required);
}
/**
* Adds a new argument with no short code.
*
* @param longArg
* @param argType
* @param defaultVal
* @param description
* @return
*/
public ArgumentParser addArgument(String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) {
return addArgument(null, longArg, argType, defaultVal, description, usageName, required);
}
/**
* Adds a new argument with no long code, and no default value.
*
* @param shortArg
* @param argType
* @param description
* @return
*/
public ArgumentParser addArgument(Character shortArg, Type argType, String description, String usageName, boolean required) {
return addArgument(shortArg, null, argType, null, description, usageName, required);
}
/**
* Adds a new argument with no short code, and no default value.
*
* @param longArg
* @param argType
* @param description
* @return
*/
public ArgumentParser addArgument(String longArg, Type argType, String description, String usageName, boolean required) {
return addArgument(null, longArg, argType, null, description, usageName, required);
}
/**
* Adds a new flag.
*
* @param shortArg
* @param longArg
* @param description
* @return
*/
public ArgumentParser addFlag(Character shortArg, String longArg, String description) {
return addArgument0(shortArg, longArg, Type.BOOLEAN, null, description, null, false);
}
/**
* Adds a new flag with no short code.
*
* @param longArg
* @param description
* @return
*/
public ArgumentParser addFlag(String longArg, String description) {
return addArgument0(null, longArg, Type.BOOLEAN, null, description, null, false);
}
/**
* Adds a new flag with no long code.
*
* @param shortArg
* @param description
* @return
*/
public ArgumentParser addFlag(Character shortArg, String description) {
return addArgument0(shortArg, null, Type.BOOLEAN, null, description, null, false);
}
/**
* Adds a description to the ArgumentParser object, which is used when
* building the description returned by getBuiltDescription.
*
* @param description
* @return
*/
public ArgumentParser addDescription(String description) {
this.description = description;
return this;
}
/**
* Builds a description of this ArgumentParser, which may be useful as
* "Usage" text to provide to a user if a call to match throws a
* ValidationException, for instance.
*
* @return
*/
public String getBuiltDescription() {
StringBuilder b = new StringBuilder();
//Now, we need to go through and get all the switch names in alphabetical
//order.
b.append("\t").append(this.description).append("\n\n");
List<Character> shortCodes = new ArrayList<Character>();
List<String> longCodes = new ArrayList<String>();
List<Character> shortCodesDone = new ArrayList<Character>();
List<String> longCodesDone = new ArrayList<String>();
List<String> aliases = new ArrayList<String>();
for (Argument arg : argumentModel) {
if (arg.shortArg != null) {
shortCodes.add(arg.shortArg);
}
if (arg.longArg != null) {
longCodes.add(arg.longArg);
}
if (arg.shortArg != null && arg.longArg != null) {
aliases.add(arg.longArg);
}
}
Collections.sort(shortCodes);
Collections.sort(longCodes);
//Go through the flags first
boolean hasShortCodeFlags = false;
StringBuilder flags = new StringBuilder();
List<Character> shortFlags = new ArrayList<Character>();
List<String> longFlags = new ArrayList<String>();
List<Character> shortArguments = new ArrayList<Character>();
List<String> longArguments = new ArrayList<String>();
for (Character c : shortCodes) {
Argument a = getArgument(c);
if (a.isFlag()) {
shortCodesDone.add(c);
flags.append(a.generateDescription(true));
hasShortCodeFlags = true;
shortFlags.add(c);
} else {
shortArguments.add(c);
}
}
for (String s : longCodes) {
Argument a = getArgument(s);
if (a.isFlag()) {
longCodesDone.add(s);
flags.append(a.generateDescription(false));
if (!aliases.contains(s)) {
longFlags.add(s);
}
} else if (!aliases.contains(s)) {
longArguments.add(s);
}
}
b.append("Usage:\n\t");
//Get the short flags first, then the long flags, then the short arguments, then the long arguments
List<String> parts = new ArrayList<String>();
if (!shortFlags.isEmpty()) {
StringBuilder usage = new StringBuilder();
usage.append("[-");
for (Character c : shortFlags) {
usage.append(c);
}
usage.append("]");
parts.add(usage.toString());
}
for (String s : longFlags) {
parts.add("[--" + s + "]");
}
List<Argument> usageList = new ArrayList<Argument>();
for (Character c : shortArguments) {
usageList.add(getArgument(c));
}
for (String s : longArguments) {
usageList.add(getArgument(s));
}
for (Argument a : usageList) {
StringBuilder usage = new StringBuilder();
if (!a.required) {
usage.append("[");
}
if (a.shortArg != null) {
usage.append("-").append(a.shortArg);
} else {
usage.append("--").append(a.longArg);
}
usage.append(" <");
if (a.isNumeric()) {
usage.append("#");
}
usage.append(a.usageName);
if (a.isArray()) {
usage.append(", ...");
}
usage.append(">");
if(a.defaultVal != null && !"".equals(a.defaultVal)){
usage.append(" (default ");
if(a.argType == Type.STRING){
usage.append("\"");
}
usage.append(a.defaultVal);
if(a.argType == Type.STRING){
usage.append("\"");
}
usage.append(")");
}
if (!a.required) {
usage.append("]");
}
parts.add(usage.toString());
}
//Now, if the default switch exists, put it here too
if (getArgument() != null) {
parts.add("<" + getArgument().usageName + ", ...>");
}
{
StringBuilder usage = new StringBuilder();
boolean first = true;
for (String part : parts) {
if (!first) {
b.append(" ");
}
first = false;
b.append(part);
}
if(parts.isEmpty()){
usage.append("No arguments.");
}
b.append(usage.toString());
}
b.append("\n\nOptions:\n\n");
Argument def = getArgument();
if (def != null && def.description != null) {
b.append(def.generateDescription(false));
}
if (flags.length() != 0) {
b.append("Flags");
if (hasShortCodeFlags) {
b.append(" (Short flags may be combined)");
}
b.append(":\n");
b.append(flags.toString());
b.append("\n");
}
if(shortCodes.isEmpty() && longCodes.isEmpty() && def == null && flags.length() == 0){
b.append("\tNo flags or options.\n");
} else {
if(shortCodes.isEmpty() && longCodes.isEmpty() && def == null){
b.append("\tNo options.\n");
} else if(flags.length() == 0){
b.append("\tNo flags.\n");
}
}
for (Character c : shortCodes) {
if (!shortCodesDone.contains(c)) {
b.append(getArgument(c).generateDescription(true));
}
}
for (String s : longCodes) {
if (!longCodesDone.contains(s)) {
b.append(getArgument(s).generateDescription(false));
}
}
return b.toString();
}
/**
* Returns just the description that was registered with {@see #addDescription(String)}.
* @return The description, or null, if one has not been set yet.
* @see #getBuiltDescription()
*/
public String getDescription(){
return description;
}
/**
* This method takes a raw string, which represents the arguments as a
* single string. It supports quoted arguments (both single and double), so
* "this example" would make the string "this example" one argument instead
* of two. Quotes may be escaped with a backslash, like so: "\"quote\"".
* Also, all arguments that start with a dash are considered flags, if you
* need a literal dash, escape it with a backslash too, \-. Instead of
* quoting arguments, you could also add a \ in front of a space to make it
* a literal space,
* <code>like\ this</code>. Within a string, to add a literal backslash in
* front of an otherwise escapable character, use two backslashes
* <code>"like this\\"</code>
*
* @param args
* @return
*/
public ArgumentParserResults match(String args) throws ValidationException {
return parse(lex(args));
}
/**
* Returns a simple List of the arguments, parsed into a proper argument list.
* This will work essentially identically to how general shell arguments are parsed.
* @param args
* @return
*/
static List<String> lex(String args) {
//First, we have to tokenize the strings. Since we can have quoted arguments, we can't simply split on spaces.
List<String> arguments = new ArrayList<String>();
StringBuilder buf = new StringBuilder();
boolean state_in_single_quote = false;
boolean state_in_double_quote = false;
for (int i = 0; i < args.length(); i++) {
Character c0 = args.charAt(i);
Character c1 = i + 1 < args.length() ? args.charAt(i + 1) : null;
if (c0 == '\\') {
if (c1 == '\'' && state_in_single_quote
|| c1 == '"' && state_in_double_quote
|| c1 == ' ' && !state_in_double_quote && !state_in_single_quote
|| c1 == '\\' && ( state_in_double_quote || state_in_single_quote )) {
//We are escaping the next character. Add it to the buffer instead, and
//skip ahead two
buf.append(c1);
i++;
continue;
}
}
if (c0 == ' ') {
if (!state_in_double_quote && !state_in_single_quote) {
//argument split
if (buf.length() != 0) {
arguments.add(buf.toString());
buf = new StringBuilder();
}
continue;
}
}
if (c0 == '\'' && !state_in_double_quote) {
if (state_in_single_quote) {
state_in_single_quote = false;
arguments.add(buf.toString());
buf = new StringBuilder();
} else {
if (buf.length() != 0) {
arguments.add(buf.toString());
buf = new StringBuilder();
}
state_in_single_quote = true;
}
continue;
}
if (c0 == '"' && !state_in_single_quote) {
if (state_in_double_quote) {
state_in_double_quote = false;
arguments.add(buf.toString());
buf = new StringBuilder();
} else {
if (buf.length() != 0) {
arguments.add(buf.toString());
buf = new StringBuilder();
}
state_in_double_quote = true;
}
continue;
}
buf.append(c0);
}
if (buf.length() != 0) {
arguments.add(buf.toString());
}
return arguments;
}
private ArgumentParserResults parse(List<String> args) throws ValidationException {
ArgumentParserResults results = new ArgumentParserResults();
//Fill in results with all the defaults
for (Argument arg : argumentModel) {
if (arg.defaultVal != null) {
//For flags, we simply don't add them if they default to false.
if (!arg.isFlag() || ( arg.isFlag() && arg.defaultVal != null )) {
Argument newArg = new Argument(arg);
newArg.setValue(arg.defaultVal);
results.updateArgument(newArg);
}
}
}
//These are arguments that are not flags.
List<String> looseArgs = new ArrayList<>();
Argument lastArg = null;
for (String arg : args) {
if (arg.matches("^[\\\\]+-.*$")) {
//This is an argument that starts with a literal dash, but we need
//to pull out this first backslash
looseArgs.add(arg.substring(1));
continue;
}
//Our regexes have a star, because -(-) is a valid argument that is an empty string.
//"" != null.
if (arg.matches("--[a-zA-Z0-9\\-]*")) {
//Finish up the last argument
results.updateArgument(validateArgument(lastArg, looseArgs));
//This is a long arg, and so it is the only one.
arg = arg.substring(2);
lastArg = getArgument(arg);
continue;
}
if (arg.matches("-[a-zA-Z0-9]*")) {
//Finish up the last argument
results.updateArgument(validateArgument(lastArg, looseArgs));
//This is a short arg, but it could be multiple letters (therefore multiple flags)
//At most, one of these can be a non-flag argument.
boolean hasNonFlagArg = false;
char lastNonFlag = ' ';
for (int i = 1; i < arg.length(); i++) {
Character c = arg.charAt(i);
Argument vArg = getArgument(c);
if(vArg == null){
throw new ValidationException("Unrecognized flag: " + c);
}
if (!vArg.isFlag() && hasNonFlagArg) {
//We have already come across a non-flag argument, and since this one isn't
//a flag, we need to throw an exception.
throw new ValidationException("Cannot combine multiple non-flag arguments using the short form. Found '" + c
+ "' but had already found '" + lastNonFlag + "'. You must split these into multiple arguments, even if they"
+ " do not have any parameters, for instance, -" + c + " -" + lastNonFlag);
}
//Is this a non flag?
if (!vArg.isFlag()) {
hasNonFlagArg = true;
lastNonFlag = c;
lastArg = vArg;
//This is all we need to do, it will be dealt with by the next iteration
} else {
//Since it's just a flag, we don't need to worry about it regarding loose arguments, so we
//are just going to go ahead and add it to the results.
results.updateArgument(vArg);
}
}
continue;
}
//It's just a loose arg, so we'll add it to the list and deal with it at the end
looseArgs.add(arg);
}
//Finish up the last argument
results.updateArgument(validateArgument(lastArg, looseArgs));
if(looseArgs.size() > 0){
//There are loose arguments left, so add them to the loose argument list.
results.updateArgument(validateArgument(null, looseArgs));
}
//TODO: Check to see if all the required values are here
return results;
}
private Argument validateArgument(Argument arg, List<String> looseArgs) throws ValidationException {
if (arg == null) {
if(!looseArgs.isEmpty()){
//All the loose arguments are accounted for. Either we're done with the arguments,
//or we just hit a -(-)specifier, so we can stop parsing these, and go ahead and
//add them to the results.
Argument a = new Argument(ArgumentParser.this.getArgument());
a.setValue(looseArgs);
looseArgs.clear();
return a;
}
return null;
}
Argument finishedArgument = new Argument(arg);
if (arg.isSingle()) {
//Just the first loose argument is associated with this argument,
//the rest (if any) belong to the default loose argument list.
//Of course, looseArgs could be empty, in which case we won't add anything to the list.
if (looseArgs.size() > 0) {
String looseArg = looseArgs.get(0);
looseArgs.remove(0);
finishedArgument.setValue(looseArg);
if (arg.isNumeric()) {
try {
Double.parseDouble(looseArg);
}
catch (NumberFormatException e) {
throw new ValidationException("Expecting a numeric value, but \"" + looseArg + "\" was encountered.");
}
}
} else {
finishedArgument.setValue("");
}
} else if (arg.isArray()) {
finishedArgument.setValue(looseArgs);
if (arg.isNumeric()) {
for (String val : looseArgs) {
try {
Double.parseDouble(val);
}
catch (NumberFormatException e) {
throw new ValidationException("Expecting a numeric value, but \"" + val + "\" was encountered.");
}
}
}
looseArgs.clear();
}
return finishedArgument;
}
/**
* This method assumes that the arguments have already been parsed out, so,
* for instance, if an argument inside of args[x] contains spaces, it will
* still be considered one argument. So, ["args with spaces", "args"] may
* have looked like this originally:
* <code>"args with spaces" args </code> but through some means or another,
* you have already parsed the arguments out.
*
* @param args
* @return
*/
public ArgumentParserResults match(String[] args) throws ValidationException {
return parse(Arrays.asList(args));
}
}