Skip to content
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

Merged
merged 23 commits into from
May 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
605630f
Add ability to support different types of quotation marks
Chris-Johnston Jan 28, 2018
2290a62
merge conflicts to update with latest branch
Chris-Johnston Jan 28, 2018
384b0ca
Added normal quotation mark to list of aliases, removed single quote …
Chris-Johnston Jan 28, 2018
4f6e29b
clean up leftover changes from testing
Chris-Johnston Jan 28, 2018
3e54e6f
change quotation mark parsing to use a map of matching pairs
Chris-Johnston Jan 29, 2018
2e27d26
remove commented out code
Chris-Johnston Jan 29, 2018
6633a5c
Fix conventions of the command parser utility functions
Chris-Johnston Jan 29, 2018
d42936c
change storage type of alias dictionary to be IReadOnlyDictionary
Chris-Johnston Jan 29, 2018
46e9cf6
revert type of CommandServiceConfig QuotationMarkAliasMap to Dictionary
Chris-Johnston Jan 29, 2018
9c58d0e
minor formatting changes to CommandParser
Chris-Johnston Jan 29, 2018
23ba23b
remove unnecessary whitespace
Chris-Johnston Jan 29, 2018
1a8aa96
Move aliases outside of CommandInfo class
Chris-Johnston Jan 29, 2018
f0fe5d6
copy IReadOnlyDictionary to ImmutableDictionary
Chris-Johnston Jan 29, 2018
8187f35
minor syntax changes in CommandServiceConfig
Chris-Johnston Jan 29, 2018
befb65e
add newline before namespace for consistency
Chris-Johnston Jan 30, 2018
2cb6750
newline formatting tweak
Chris-Johnston Feb 1, 2018
7fbd0ab
simplification of GetMatch method for CommandParser
Chris-Johnston Feb 3, 2018
ac394c2
add more quote unicode punctuation pairs
Chris-Johnston Feb 3, 2018
12d5a70
add check for null value when building ImmutableDictionary
Chris-Johnston Feb 3, 2018
fd3533f
Move default alias map into a separate source file
Chris-Johnston Feb 5, 2018
89ae95b
Merge branch 'dev' into quotationMarks
Chris-Johnston Mar 27, 2018
0704eab
Merge branch 'dev' into quotationMarks
Chris-Johnston May 12, 2018
820f9e3
Ensure that the collection passed into command service is not null
Chris-Johnston May 12, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions src/Discord.Net.Commands/CommandParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Threading.Tasks;
Expand All @@ -13,8 +14,7 @@ private enum ParserPart
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);
Expand All @@ -24,7 +24,27 @@ 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 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++)
{
Expand Down Expand Up @@ -74,9 +94,11 @@ public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, IComma
argBuilder.Append(c);
continue;
}
if (c == '\"')

if (IsOpenQuote(aliasMap, c))
{
curPart = ParserPart.QuotedParameter;
matchQuote = GetMatch(aliasMap, c);
continue;
}
curPart = ParserPart.Parameter;
Expand All @@ -97,7 +119,7 @@ public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, IComma
}
else if (curPart == ParserPart.QuotedParameter)
{
if (c == '\"')
if (c == matchQuote)
{
argString = argBuilder.ToString(); //Remove quotes
lastArgEndPos = curPos + 1;
Expand Down
5 changes: 3 additions & 2 deletions src/Discord.Net.Commands/CommandService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down Expand Up @@ -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);
Expand All @@ -45,6 +46,7 @@ public CommandService(CommandServiceConfig config)
_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.");

Expand Down Expand Up @@ -333,7 +335,6 @@ public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IServiceP
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;
Expand Down
5 changes: 5 additions & 0 deletions src/Discord.Net.Commands/CommandServiceConfig.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace Discord.Commands
{
Expand All @@ -18,6 +19,10 @@ 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; } = QuotationAliasUtils.GetDefaultAliasMap;

/// <summary> Determines whether extra parameters should be ignored. </summary>
public bool IgnoreExtraArgs { get; set; } = false;
}
Expand Down
5 changes: 3 additions & 2 deletions src/Discord.Net.Commands/Info/CommandInfo.cs
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;
Expand Down Expand Up @@ -121,7 +121,8 @@ public async Task<ParseResult> ParseAsync(ICommandContext context, int startInde
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)
Expand Down
95 changes: 95 additions & 0 deletions src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs
Original file line number Diff line number Diff line change
@@ -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> {
{'\"', '\"' },
{'«', '»' },
{'‘', '’' },
{'“', '”' },
{'„', '‟' },
{'‹', '›' },
{'‚', '‛' },
{'《', '》' },
{'〈', '〉' },
{'「', '」' },
{'『', '』' },
{'〝', '〞' },
{'﹁', '﹂' },
{'﹃', '﹄' },
{'"', '"' },
{''', ''' },
{'「', '」' },
{'(', ')' },
{'༺', '༻' },
{'༼', '༽' },
{'᚛', '᚜' },
{'⁅', '⁆' },
{'⌈', '⌉' },
{'⌊', '⌋' },
{'❨', '❩' },
{'❪', '❫' },
{'❬', '❭' },
{'❮', '❯' },
{'❰', '❱' },
{'❲', '❳' },
{'❴', '❵' },
{'⟅', '⟆' },
{'⟦', '⟧' },
{'⟨', '⟩' },
{'⟪', '⟫' },
{'⟬', '⟭' },
{'⟮', '⟯' },
{'⦃', '⦄' },
{'⦅', '⦆' },
{'⦇', '⦈' },
{'⦉', '⦊' },
{'⦋', '⦌' },
{'⦍', '⦎' },
{'⦏', '⦐' },
{'⦑', '⦒' },
{'⦓', '⦔' },
{'⦕', '⦖' },
{'⦗', '⦘' },
{'⧘', '⧙' },
{'⧚', '⧛' },
{'⧼', '⧽' },
{'⸂', '⸃' },
{'⸄', '⸅' },
{'⸉', '⸊' },
{'⸌', '⸍' },
{'⸜', '⸝' },
{'⸠', '⸡' },
{'⸢', '⸣' },
{'⸤', '⸥' },
{'⸦', '⸧' },
{'⸨', '⸩' },
{'【', '】'},
{'〔', '〕' },
{'〖', '〗' },
{'〘', '〙' },
{'〚', '〛' }
};
}
}
}
}