Skip to content

Instantly share code, notes, and snippets.

@gravityfox
Created January 1, 2016 09:53
Show Gist options
  • Save gravityfox/41a84a5cdfda797dd7d1 to your computer and use it in GitHub Desktop.
Save gravityfox/41a84a5cdfda797dd7d1 to your computer and use it in GitHub Desktop.
One command parser to rule them all.
public ParseResult parse2() throws CommandException {
ParseResult parseResult = new ParseResult();
boolean inQuote = false;
// Regex Pattern for identifying arguments and flags. It respects quotation marks and escape characters.
Pattern pattern = Pattern.compile(regex);
// Check for unclosed quotes
{
String toStrip = arguments;
while (true) {
Matcher tempMatcher = pattern.matcher(toStrip);
if (!tempMatcher.find()) break;
toStrip = toStrip.substring(0, tempMatcher.start()) + toStrip.substring(tempMatcher.end());
}
Pattern pattern2 = Pattern.compile("[\"']");
Matcher matcher = pattern2.matcher(toStrip);
if (matcher.find()) {
if (autoCloseQuotes) {
if (matcher.group().equals("\"")) arguments += "\"";
else if (matcher.group().equals("'")) arguments += "'";
inQuote = true;
} else {
throw new CommandException(Texts.of("You must close all quotes!"));
}
}
}
// List of string arguments that were not parsed as flags
List<String> argsList = new ArrayList<>();
// Matcher for identifying arguments and flags.
Matcher matcher = pattern.matcher(arguments);
boolean lastIsCurrent = !(arguments.length() == 0) && (inQuote || !arguments.substring(arguments.length() - 1).matches("[\"']"));
// Iterate through matches
while (matcher.find()) {
String result = matcher.group();
boolean include = true;
if (excludeCurrent && lastIsCurrent && matcher.end() == arguments.length())
include = false;
// Makes "---" mark the end of the command. Effectively allows command comments
// It also means that flag names cannot start with hyphens
if (!result.startsWith("---")) {
// Throws out any results that are empty
if (result.equals("--")) {
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.LONGFLAGKEY, "", 0, "");
continue;
} else if (result.equals("-")) {
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.SHORTFLAG, "", 0, "");
continue;
}
// Parses result as long flag.
// Format is --<flagname>:<value> Where value can be a quoted string. "=" is also a valid separator
// If a limit is specified, the flags will be cut out of the final string
// Setting extractSubFlags to true forces flags within the final string to be left as-is
// This is useful if the final string is it's own command and needs to be re-parsed
if (result.startsWith("--") && (extractSubFlags || limit == 0 || argsList.size() < limit || (parseLastFlags && argsList.size() <= limit))) {
// Trims the prefix
result = result.substring(2);
// Splits once by ":" or "="
String[] parts = result.split("[:=]", 2);
// Throw an exception if the key contains a quote character, as that shouldn't be allowed
if (parts[0].contains("\"") || parts[0].contains("'"))
throw new CommandException(Texts.of("You may not have quotes in long flag keys!"));
// Default value in case a value isn't specified
String value = "";
// Retrieves value if it exists
if (parts.length > 1) {
value = unescapeString(parts[1]);
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.LONGFLAGVALUE, value, 0, parts[0]);
} else {
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.LONGFLAGKEY, parts[0], 0, "");
}
if (include) {
// Applies the flagMapper function.
// This is a destructive function that takes 3 parameters and returns nothing. (Destructive consumer)
flagMapper.apply(parseResult.flagmap)
.apply(parts[0])
.accept(value);
}
// Parses result as a short flag. Limit behavior is the same as long flags
// multiple letters are treated as multiple flags. Repeating letters add a second flag with a repetition
// Example: "-aab" becomes flags "a", "aa", and "b"
} else if (result.startsWith("-") && result.substring(1).matches("^.*[^\\d\\.].*$")
&& (extractSubFlags || limit == 0 || argsList.size() < limit || (parseLastFlags && argsList.size() <= limit))) {
// Trims prefix
result = result.substring(1);
// Iterates through each letter
for (String str : result.split("")) {
// Checks to make sure that the flag letter is alphabetic. Throw exception if it doesn't
if (str.matches("[a-zA-Z]")) {
// Checks if the flag already exists, and repeat the letter until it doesn't
String temp = str;
while (parseResult.flagmap.containsKey(temp)) {
temp += str;
}
// Applies destructive flagMapper function.
flagMapper.apply(parseResult.flagmap).apply(temp).accept("");
} else if (str.matches("[:=-]")) {
throw new CommandException(Texts.of("You may only have alphabetic short flags! Did you mean to use a long flag (two dashes)?"));
} else {
throw new CommandException(Texts.of("You may only have alphabetic short flags!"));
}
}
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.SHORTFLAG, "", 0, "");
// Simply adds the result to the argument list. Quotes are trimmed.
// Fallback if the result isn't a flag.
} else {
if (leaveFinalAsIs && limit != 0 && argsList.size() >= limit) {
argsList.add(arguments.substring(matcher.start(), arguments.length() - (inQuote ? 1 : 0)));
break;
}
if (limit != 0 && argsList.size() >= limit && !unescapeLast) {
argsList.add(result);
} else {
argsList.add(unescapeString(result));
}
}
} else {
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.COMMENT, arguments.substring(matcher.start() + 3), 0, "");
break;
}
}
if (!lastIsCurrent && (parseResult.currentElement == null || parseResult.currentElement.type != CurrentElement.ElementType.COMMENT)) {
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.ARGUMENT, "", argsList.size(), "");
}
// This part converts the argument list to the final argument array.
// A number of arguments are copied to a new list less than or equal to the limit.
// The rest of the arguments, if any, are concatenated together.
List<String> finalList = new ArrayList<>();
String finalString = "";
for (int i = 0; i < argsList.size(); i++) {
if (limit == 0 || i < limit) {
finalList.add(argsList.get(i));
} else {
finalString += argsList.get(i);
if (i + 1 < argsList.size()) {
finalString += " ";
}
}
}
if (!finalString.isEmpty()) {
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.FINAL, finalString, finalList.size(), "");
finalList.add(finalString);
} else if (parseResult.currentElement == null) {
parseResult.currentElement = new CurrentElement(CurrentElement.ElementType.ARGUMENT, finalList.get(finalList.size() - 1), finalList.size() - 1, "");
}
// Converts final argument list to an array.
parseResult.args = finalList.toArray(new String[finalList.size()]);
return parseResult;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment