Created
January 1, 2016 09:53
-
-
Save gravityfox/41a84a5cdfda797dd7d1 to your computer and use it in GitHub Desktop.
One command parser to rule them all.
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 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