-
-
Notifications
You must be signed in to change notification settings - Fork 742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for parsing multiple types of quotation marks in commands, Fix #942 #943
Changes from 9 commits
605630f
2290a62
384b0ca
4f6e29b
3e54e6f
2e27d26
6633a5c
d42936c
46e9cf6
9c58d0e
23ba23b
1a8aa96
f0fe5d6
8187f35
befb65e
2cb6750
7fbd0ab
ac394c2
12d5a70
fd3533f
89ae95b
0704eab
820f9e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
@@ -13,7 +14,7 @@ private enum ParserPart | |
Parameter, | ||
QuotedParameter | ||
} | ||
|
||
public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos) | ||
{ | ||
ParameterInfo curParam = null; | ||
|
@@ -24,7 +25,32 @@ public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, IComma | |
var argList = ImmutableArray.CreateBuilder<TypeReaderResult>(); | ||
var paramList = ImmutableArray.CreateBuilder<TypeReaderResult>(); | ||
bool isEscaping = false; | ||
char c; | ||
char c, matchQuote = '\0'; | ||
|
||
// local helper functions | ||
bool IsOpenQuote(IReadOnlyDictionary<char, char> dict, char ch) | ||
{ | ||
// return if the key is contained in the dictionary if it exists | ||
if (dict != null) | ||
return dict.ContainsKey(ch); | ||
// or otherwise if it is the default double quote | ||
return c == '\"'; | ||
} | ||
|
||
char GetMatch(IReadOnlyDictionary<char, char> dict, char ch) | ||
{ | ||
// get the corresponding value for the key, if it exists | ||
if (dict != null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could just be reduced down to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AntiTcb How about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or just make sure that the collection that gets passed in is always initialized so that it's at least empty? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of assuming that the collection is always initialized. Added in 820f9e3 |
||
{ | ||
char value; | ||
if (dict.TryGetValue(c, out value)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{ | ||
return value; | ||
} | ||
} | ||
// or get the default pair of the default double quote | ||
return '\"'; | ||
} | ||
|
||
for (int curPos = startPos; curPos <= endPos; curPos++) | ||
{ | ||
|
@@ -74,9 +100,11 @@ public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, IComma | |
argBuilder.Append(c); | ||
continue; | ||
} | ||
if (c == '\"') | ||
|
||
if(IsOpenQuote(command._quotationAliases, c)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing whitespace after if |
||
{ | ||
curPart = ParserPart.QuotedParameter; | ||
matchQuote = GetMatch(command._quotationAliases, c); | ||
continue; | ||
} | ||
curPart = ParserPart.Parameter; | ||
|
@@ -97,7 +125,7 @@ public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, IComma | |
} | ||
else if (curPart == ParserPart.QuotedParameter) | ||
{ | ||
if (c == '\"') | ||
if(c == matchQuote) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing whitespace after if |
||
{ | ||
argString = argBuilder.ToString(); //Remove quotes | ||
lastArgEndPos = curPos + 1; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ public class CommandService | |
internal readonly RunMode _defaultRunMode; | ||
internal readonly Logger _cmdLogger; | ||
internal readonly LogManager _logManager; | ||
internal readonly IReadOnlyDictionary<char, char> _quotationMarkAliasMap; | ||
|
||
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); | ||
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); | ||
|
@@ -45,6 +46,7 @@ public CommandService(CommandServiceConfig config) | |
_ignoreExtraArgs = config.IgnoreExtraArgs; | ||
_separatorChar = config.SeparatorChar; | ||
_defaultRunMode = config.DefaultRunMode; | ||
_quotationMarkAliasMap = config.QuotationMarkAliasMap; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make an Well, I guess the consideration is: do we want to guarantee immutable semantics internally, or fake it and take a perf win but also allow the map to be mutated while the client is running (which isn't too hard to do while implemented this way)? cc: @foxbot @FiniteReality Either way, I'd make the storage field an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For completion's sake: There is a third option where we can fake the immutable semantics, but make it much harder for users to modify the map after initialization. (Still wouldn't completely prevent it, though.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now, I've left this as an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I didn't see your second comment. I think I ended up doing your third option. I've been able to test it like this: CommandServiceConfig config = new CommandServiceConfig();
var d = new Dictionary<char, char>()
{
{ '-', '-' },
{'\"', '\"' }
};
config.QuotationMarkAliasMap = d;
commands = new CommandService(config);
d.Add('~', '~'); Again, I've found that it still works but might not be the intended idea. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah, that's the first thing that I talked about. The third option would be to do _quotationMarkAliasMap = new Dictionary(config.QuotationMarkAliasMap); Also, the type on the new CommandServiceConfig()
{
QuotationMarkAliasMap =
{
{ '[', ']' } //for example
}
}; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But anyway, I'd like to hear fox and finite's inputs. Do we want the internals to be semantically correct, or not take a perf hit? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say make an ImmutableDictionary out of it, because that's what we do in other places . |
||
if (_defaultRunMode == RunMode.Default) | ||
throw new InvalidOperationException("The default run mode cannot be set to Default."); | ||
|
||
|
@@ -277,7 +279,6 @@ public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IServiceP | |
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | ||
{ | ||
services = services ?? EmptyServiceProvider.Instance; | ||
|
||
var searchResult = Search(context, input); | ||
if (!searchResult.IsSuccess) | ||
return searchResult; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
namespace Discord.Commands | ||
using System.Collections.Generic; | ||
namespace Discord.Commands | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Newline between these for consistency |
||
{ | ||
public class CommandServiceConfig | ||
{ | ||
|
@@ -16,6 +17,30 @@ public class CommandServiceConfig | |
/// <summary> Determines whether RunMode.Sync commands should push exceptions up to the caller. </summary> | ||
public bool ThrowOnError { get; set; } = true; | ||
|
||
/// <summary> Collection of aliases that can wrap strings for command parsing. | ||
/// represents the opening quotation mark and the value is the corresponding closing mark.</summary> | ||
public Dictionary<char, char> QuotationMarkAliasMap { get; set; } | ||
= new Dictionary<char, char>() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant parentheses here. Do we need the type name too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type name is required until dotnet/csharplang#100 lands. Do we omit the parentheses in other places too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure we omit the parentheses where possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GitHub doesn't seem to show this as outdated correctly, but please see commit 8187f35 |
||
{ | ||
{'\"', '\"' }, | ||
{'«', '»' }, | ||
{'‘', '’' }, | ||
{'“', '”' }, | ||
{'„', '‟' }, | ||
{'‹', '›' }, | ||
{'‚', '‛' }, | ||
{'《', '》' }, | ||
{'〈', '〉' }, | ||
{'「', '」' }, | ||
{'『', '』' }, | ||
{'〝', '〞' }, | ||
{'﹁', '﹂' }, | ||
{'﹃', '﹄' }, | ||
{'"', '"' }, | ||
{''', ''' }, | ||
{'「', '」' } | ||
}; | ||
|
||
/// <summary> Determines whether extra parameters should be ignored. </summary> | ||
public bool IgnoreExtraArgs { get; set; } = false; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
using Discord.Commands.Builders; | ||
using Discord.Commands.Builders; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
|
@@ -20,6 +20,7 @@ public class CommandInfo | |
|
||
private readonly CommandService _commandService; | ||
private readonly Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> _action; | ||
internal readonly IReadOnlyDictionary<char,char> _quotationAliases; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO this should not be stored on the command, but I'm not sure if we have the ability to pass the command service to the parser where ParseArgsAsync is called. Might be worth taking a look? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked at this again, and found that I could use the similar behavior of |
||
|
||
public ModuleInfo Module { get; } | ||
public string Name { get; } | ||
|
@@ -65,6 +66,7 @@ internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService s | |
HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; | ||
|
||
_action = builder.Callback; | ||
_quotationAliases = service._quotationMarkAliasMap; | ||
_commandService = service; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove unnecessary whitespace