* Add ability to support different types of quotation marks * Added normal quotation mark to list of aliases, removed single quote mark * clean up leftover changes from testing * change quotation mark parsing to use a map of matching pairs * remove commented out code * Fix conventions of the command parser utility functions * change storage type of alias dictionary to be IReadOnlyDictionary * revert type of CommandServiceConfig QuotationMarkAliasMap to Dictionary * minor formatting changes to CommandParser * remove unnecessary whitespace * Move aliases outside of CommandInfo class * copy IReadOnlyDictionary to ImmutableDictionary * minor syntax changes in CommandServiceConfig * add newline before namespace for consistency * newline formatting tweak * simplification of GetMatch method for CommandParser * add more quote unicode punctuation pairs * add check for null value when building ImmutableDictionary * Move default alias map into a separate source file * Ensure that the collection passed into command service is not nullpull/1016/merge
@@ -1,4 +1,5 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
@@ -13,8 +14,7 @@ namespace Discord.Commands | |||
Parameter, | |||
QuotedParameter | |||
} | |||
public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) | |||
public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos, IReadOnlyDictionary<char, char> aliasMap) | |||
{ | |||
ParameterInfo curParam = null; | |||
StringBuilder argBuilder = new StringBuilder(input.Length); | |||
@@ -24,7 +24,27 @@ namespace Discord.Commands | |||
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 is populated | |||
if (dict.Count != 0) | |||
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 | |||
// and if the dictionary is populated | |||
if (dict.Count != 0 && dict.TryGetValue(c, out var value)) | |||
return value; | |||
// or get the default pair of the default double quote | |||
return '\"'; | |||
} | |||
for (int curPos = startPos; curPos <= endPos; curPos++) | |||
{ | |||
@@ -74,9 +94,11 @@ namespace Discord.Commands | |||
argBuilder.Append(c); | |||
continue; | |||
} | |||
if (c == '\"') | |||
if (IsOpenQuote(aliasMap, c)) | |||
{ | |||
curPart = ParserPart.QuotedParameter; | |||
matchQuote = GetMatch(aliasMap, c); | |||
continue; | |||
} | |||
curPart = ParserPart.Parameter; | |||
@@ -97,7 +119,7 @@ namespace Discord.Commands | |||
} | |||
else if (curPart == ParserPart.QuotedParameter) | |||
{ | |||
if (c == '\"') | |||
if (c == matchQuote) | |||
{ | |||
argString = argBuilder.ToString(); //Remove quotes | |||
lastArgEndPos = curPos + 1; | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -32,6 +32,7 @@ namespace Discord.Commands | |||
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 @@ namespace Discord.Commands | |||
_ignoreExtraArgs = config.IgnoreExtraArgs; | |||
_separatorChar = config.SeparatorChar; | |||
_defaultRunMode = config.DefaultRunMode; | |||
_quotationMarkAliasMap = (config.QuotationMarkAliasMap ?? new Dictionary<char, char>()).ToImmutableDictionary(); | |||
if (_defaultRunMode == RunMode.Default) | |||
throw new InvalidOperationException("The default run mode cannot be set to Default."); | |||
@@ -337,7 +339,6 @@ namespace Discord.Commands | |||
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||
{ | |||
services = services ?? EmptyServiceProvider.Instance; | |||
var searchResult = Search(context, input); | |||
if (!searchResult.IsSuccess) | |||
return searchResult; | |||
@@ -1,4 +1,5 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Discord.Commands | |||
{ | |||
@@ -18,6 +19,10 @@ namespace Discord.Commands | |||
/// <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; } = QuotationAliasUtils.GetDefaultAliasMap; | |||
/// <summary> Determines whether extra parameters should be ignored. </summary> | |||
public bool IgnoreExtraArgs { get; set; } = false; | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using Discord.Commands.Builders; | |||
using Discord.Commands.Builders; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -121,7 +121,8 @@ namespace Discord.Commands | |||
return ParseResult.FromError(preconditionResult); | |||
string input = searchResult.Text.Substring(startIndex); | |||
return await CommandParser.ParseArgsAsync(this, context, services, input, 0).ConfigureAwait(false); | |||
return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, 0, _commandService._quotationMarkAliasMap).ConfigureAwait(false); | |||
} | |||
public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) | |||
@@ -0,0 +1,95 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Globalization; | |||
namespace Discord.Commands | |||
{ | |||
/// <summary> | |||
/// Utility methods for generating matching pairs of unicode quotation marks for CommandServiceConfig | |||
/// </summary> | |||
internal static class QuotationAliasUtils | |||
{ | |||
/// <summary> | |||
/// Generates an IEnumerable of characters representing open-close pairs of | |||
/// quotation punctuation. | |||
/// </summary> | |||
internal static Dictionary<char, char> GetDefaultAliasMap | |||
{ | |||
get | |||
{ | |||
// Output of a gist provided by https://gist.github.com/ufcpp | |||
// https://gist.github.com/ufcpp/5b2cf9a9bf7d0b8743714a0b88f7edc5 | |||
// This was not used for the implementation because of incompatibility with netstandard1.1 | |||
return new Dictionary<char, char> { | |||
{'\"', '\"' }, | |||
{'«', '»' }, | |||
{'‘', '’' }, | |||
{'“', '”' }, | |||
{'„', '‟' }, | |||
{'‹', '›' }, | |||
{'‚', '‛' }, | |||
{'《', '》' }, | |||
{'〈', '〉' }, | |||
{'「', '」' }, | |||
{'『', '』' }, | |||
{'〝', '〞' }, | |||
{'﹁', '﹂' }, | |||
{'﹃', '﹄' }, | |||
{'"', '"' }, | |||
{''', ''' }, | |||
{'「', '」' }, | |||
{'(', ')' }, | |||
{'༺', '༻' }, | |||
{'༼', '༽' }, | |||
{'᚛', '᚜' }, | |||
{'⁅', '⁆' }, | |||
{'⌈', '⌉' }, | |||
{'⌊', '⌋' }, | |||
{'❨', '❩' }, | |||
{'❪', '❫' }, | |||
{'❬', '❭' }, | |||
{'❮', '❯' }, | |||
{'❰', '❱' }, | |||
{'❲', '❳' }, | |||
{'❴', '❵' }, | |||
{'⟅', '⟆' }, | |||
{'⟦', '⟧' }, | |||
{'⟨', '⟩' }, | |||
{'⟪', '⟫' }, | |||
{'⟬', '⟭' }, | |||
{'⟮', '⟯' }, | |||
{'⦃', '⦄' }, | |||
{'⦅', '⦆' }, | |||
{'⦇', '⦈' }, | |||
{'⦉', '⦊' }, | |||
{'⦋', '⦌' }, | |||
{'⦍', '⦎' }, | |||
{'⦏', '⦐' }, | |||
{'⦑', '⦒' }, | |||
{'⦓', '⦔' }, | |||
{'⦕', '⦖' }, | |||
{'⦗', '⦘' }, | |||
{'⧘', '⧙' }, | |||
{'⧚', '⧛' }, | |||
{'⧼', '⧽' }, | |||
{'⸂', '⸃' }, | |||
{'⸄', '⸅' }, | |||
{'⸉', '⸊' }, | |||
{'⸌', '⸍' }, | |||
{'⸜', '⸝' }, | |||
{'⸠', '⸡' }, | |||
{'⸢', '⸣' }, | |||
{'⸤', '⸥' }, | |||
{'⸦', '⸧' }, | |||
{'⸨', '⸩' }, | |||
{'【', '】'}, | |||
{'〔', '〕' }, | |||
{'〖', '〗' }, | |||
{'〘', '〙' }, | |||
{'〚', '〛' } | |||
}; | |||
} | |||
} | |||
} | |||
} |