Add basic flag parsing support
This commit is contained in:
		| @@ -1,4 +1,5 @@ | ||||
| using System; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| using App.Metrics; | ||||
| @@ -61,9 +62,10 @@ namespace PluralKit.Bot | ||||
|         /// </summary> | ||||
|         public bool Match(ref string used, params string[] potentialMatches) | ||||
|         { | ||||
|             var arg = PeekArgument(); | ||||
|             foreach (var match in potentialMatches) | ||||
|             { | ||||
|                 if (PeekArgument().Equals(match, StringComparison.InvariantCultureIgnoreCase)) | ||||
|                 if (arg.Equals(match, StringComparison.InvariantCultureIgnoreCase)) | ||||
|                 { | ||||
|                     used = PopArgument(); | ||||
|                     return true; | ||||
| @@ -81,6 +83,15 @@ namespace PluralKit.Bot | ||||
|             string used = null; // Unused and unreturned, we just yeet it | ||||
|             return Match(ref used, potentialMatches); | ||||
|         } | ||||
|  | ||||
|         public bool MatchFlag(params string[] potentialMatches) | ||||
|         { | ||||
|             // Flags are *ALWAYS PARSED LOWERCASE*. This means we skip out on a "ToLower" call here. | ||||
|             // Can assume the caller array only contains lowercase *and* the set below only contains lowercase | ||||
|              | ||||
|             var flags = _parameters.Flags(); | ||||
|             return potentialMatches.Any(potentialMatch => flags.Contains(potentialMatch)); | ||||
|         } | ||||
|          | ||||
|         public async Task Execute<T>(Command commandDef, Func<T, Task> handler) | ||||
|         { | ||||
|   | ||||
| @@ -12,6 +12,29 @@ namespace PluralKit.Bot | ||||
|  | ||||
|         private readonly string _cmd; | ||||
|         private int _ptr; | ||||
|         private ISet<string> _flags = null; // Only parsed when requested first time | ||||
|  | ||||
|         private struct WordPosition | ||||
|         { | ||||
|             // Start of the word | ||||
|             internal int startPos; | ||||
|              | ||||
|             // End of the word | ||||
|             internal int endPos; | ||||
|              | ||||
|             // How much to advance word pointer afterwards to point at the start of the *next* word | ||||
|             internal int advanceAfterWord; | ||||
|  | ||||
|             internal bool wasQuoted; | ||||
|  | ||||
|             public WordPosition(int startPos, int endPos, int advanceAfterWord, bool wasQuoted) | ||||
|             { | ||||
|                 this.startPos = startPos; | ||||
|                 this.endPos = endPos; | ||||
|                 this.advanceAfterWord = advanceAfterWord; | ||||
|                 this.wasQuoted = wasQuoted; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public Parameters(string cmd) | ||||
|         { | ||||
| @@ -21,43 +44,90 @@ namespace PluralKit.Bot | ||||
|             _ptr = 0; | ||||
|         } | ||||
|  | ||||
|         private void ParseFlags() | ||||
|         { | ||||
|             _flags = new HashSet<string>(); | ||||
|              | ||||
|             var ptr = 0; | ||||
|             while (NextWordPosition(ptr) is { } wp) | ||||
|             { | ||||
|                 ptr = wp.endPos + wp.advanceAfterWord; | ||||
|                  | ||||
|                 // Is this word a *flag* (as in, starts with a - AND is not quoted) | ||||
|                 if (_cmd[wp.startPos] != '-' || wp.wasQuoted) continue; // (if not, carry on w/ next word) | ||||
|                  | ||||
|                 // Find the *end* of the flag start (technically allowing arbitrary amounts of dashes) | ||||
|                 var flagNameStart = wp.startPos; | ||||
|                 while (flagNameStart < _cmd.Length && _cmd[flagNameStart] == '-') | ||||
|                     flagNameStart++; | ||||
|  | ||||
|                 // Then add the word to the flag set | ||||
|                 var word = _cmd.Substring(flagNameStart, wp.endPos - flagNameStart).Trim(); | ||||
|                 if (word.Length > 0) | ||||
|                     _flags.Add(word.ToLowerInvariant()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public string Pop() | ||||
|         { | ||||
|             var positions = NextWordPosition(); | ||||
|             if (positions == null) return ""; | ||||
|             // Loop to ignore and skip past flags | ||||
|             while (NextWordPosition(_ptr) is { } pos) | ||||
|             { | ||||
|                 _ptr = pos.endPos + pos.advanceAfterWord; | ||||
|                 if (_cmd[pos.startPos] == '-' && !pos.wasQuoted) continue; | ||||
|                 return _cmd.Substring(pos.startPos, pos.endPos - pos.startPos).Trim(); | ||||
|             } | ||||
|  | ||||
|             var (start, end, advance) = positions.Value; | ||||
|             _ptr = end + advance; | ||||
|             return _cmd.Substring(start, end - start).Trim(); | ||||
|             return ""; | ||||
|         } | ||||
|  | ||||
|         public string Peek() | ||||
|         { | ||||
|             var positions = NextWordPosition(); | ||||
|             if (positions == null) return ""; | ||||
|             // Loop to ignore and skip past flags, temp ptr so we don't move the real ptr | ||||
|             var ptr = _ptr; | ||||
|             while (NextWordPosition(ptr) is { } pos) | ||||
|             { | ||||
|                 ptr = pos.endPos + pos.advanceAfterWord; | ||||
|                 if (_cmd[pos.startPos] == '-' && !pos.wasQuoted) continue; | ||||
|                 return _cmd.Substring(pos.startPos, pos.endPos - pos.startPos).Trim(); | ||||
|             } | ||||
|  | ||||
|             var (start, end, _) = positions.Value; | ||||
|             return _cmd.Substring(start, end - start).Trim(); | ||||
|             return ""; | ||||
|         } | ||||
|  | ||||
|         public string Remainder() => _cmd.Substring(Math.Min(_ptr, _cmd.Length)).Trim(); | ||||
|         public ISet<string> Flags() | ||||
|         { | ||||
|             if (_flags == null) ParseFlags(); | ||||
|             return _flags; | ||||
|         } | ||||
|  | ||||
|         public string Remainder() | ||||
|         { | ||||
|             // Skip all *leading* flags when taking the remainder | ||||
|             while (NextWordPosition(_ptr) is {} wp) | ||||
|             { | ||||
|                 if (_cmd[wp.startPos] != '-' || wp.wasQuoted) break; | ||||
|                 _ptr = wp.endPos + wp.advanceAfterWord; | ||||
|             } | ||||
|              | ||||
|             // *Then* get the remainder | ||||
|             return _cmd.Substring(Math.Min(_ptr, _cmd.Length)).Trim(); | ||||
|         } | ||||
|          | ||||
|         public string FullCommand => _cmd; | ||||
|  | ||||
|         // Returns tuple of (startpos, endpos, advanceafter) | ||||
|         // advanceafter is how much to move the pointer afterwards to point it | ||||
|         // at the start of the next word | ||||
|         private ValueTuple<int, int, int>? NextWordPosition() | ||||
|         private WordPosition? NextWordPosition(int position) | ||||
|         { | ||||
|             // Is this the end of the string? | ||||
|             if (_cmd.Length <= _ptr) return null; | ||||
|             if (_cmd.Length <= position) return null; | ||||
|  | ||||
|             // Is this a quoted word? | ||||
|             if (_quotePairs.ContainsKey(_cmd[_ptr])) | ||||
|             if (_quotePairs.ContainsKey(_cmd[position])) | ||||
|             { | ||||
|                 // This is a quoted word, find corresponding end quote and return span | ||||
|                 var endQuote = _quotePairs[_cmd[_ptr]]; | ||||
|                 var endQuotePosition = _cmd.IndexOf(endQuote, _ptr + 1); | ||||
|                  | ||||
|                 var endQuote = _quotePairs[_cmd[position]]; | ||||
|                 var endQuotePosition = _cmd.IndexOf(endQuote, position + 1); | ||||
|  | ||||
|                 // Position after the end quote should be a space (or EOL) | ||||
|                 // Otherwise treat it as a standard word that's not quoted | ||||
|                 if (_cmd.Length == endQuotePosition + 1 || _cmd[endQuotePosition + 1] == ' ') | ||||
| @@ -66,16 +136,19 @@ namespace PluralKit.Bot | ||||
|                     { | ||||
|                         // This is an unterminated quoted word, just return the entire word including the start quote | ||||
|                         // TODO: should we do something else here? | ||||
|                         return (_ptr, _cmd.Length, 0); | ||||
|                         return new WordPosition(position, _cmd.Length, 0, false); | ||||
|                     } | ||||
|  | ||||
|                     return (_ptr + 1, endQuotePosition, 2); | ||||
|                     return new WordPosition(position + 1, endQuotePosition, 2, true); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Not a quoted word, just find the next space and return as appropriate | ||||
|             var wordEnd = _cmd.IndexOf(' ', _ptr + 1); | ||||
|             return wordEnd != -1 ? (_ptr, wordEnd, 1) : (_ptr, _cmd.Length, 0); | ||||
|             // Not a quoted word, just find the next space and return if it's the end of the command | ||||
|             var wordEnd = _cmd.IndexOf(' ', position + 1); | ||||
|              | ||||
|             return wordEnd == -1 | ||||
|                 ? new WordPosition(position, _cmd.Length, 0, false) | ||||
|                 : new WordPosition(position, wordEnd, 1, false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user