Browse Source

Rewrite command builders using C#7 features

Not completely tested as of yet
pull/678/head
FiniteReality FiniteReality 8 years ago
parent
commit
694eff54ec
21 changed files with 509 additions and 357 deletions
  1. +1
    -1
      src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs
  2. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs
  3. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs
  4. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs
  5. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs
  6. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs
  7. +16
    -59
      src/Discord.Net.Commands/Builders/CommandBuilder.cs
  8. +2
    -2
      src/Discord.Net.Commands/Builders/ModuleBuilder.cs
  9. +79
    -40
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  10. +91
    -0
      src/Discord.Net.Commands/Builders/OverloadBuilder.cs
  11. +7
    -5
      src/Discord.Net.Commands/Builders/ParameterBuilder.cs
  12. +5
    -3
      src/Discord.Net.Commands/CommandException.cs
  13. +0
    -9
      src/Discord.Net.Commands/CommandMatch.cs
  14. +5
    -5
      src/Discord.Net.Commands/CommandParser.cs
  15. +63
    -30
      src/Discord.Net.Commands/CommandService.cs
  16. +4
    -180
      src/Discord.Net.Commands/Info/CommandInfo.cs
  17. +207
    -0
      src/Discord.Net.Commands/Info/OverloadInfo.cs
  18. +4
    -2
      src/Discord.Net.Commands/Info/ParameterInfo.cs
  19. +0
    -5
      src/Discord.Net.Commands/PrimitiveParsers.cs
  20. +8
    -2
      src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs
  21. +12
    -9
      src/Discord.Net.Commands/Results/ParseResult.cs

+ 1
- 1
src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs View File

@@ -6,6 +6,6 @@ namespace Discord.Commands
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public abstract class PreconditionAttribute : Attribute public abstract class PreconditionAttribute : Attribute
{ {
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services);
public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services);
} }
} }

+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs View File

@@ -42,7 +42,7 @@ namespace Discord.Commands
GuildPermission = null; GuildPermission = null;
} }


public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services)
{ {
var guildUser = await context.Guild.GetCurrentUserAsync(); var guildUser = await context.Guild.GetCurrentUserAsync();




+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs View File

@@ -38,7 +38,7 @@ namespace Discord.Commands
Contexts = contexts; Contexts = contexts;
} }


public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services)
{ {
bool isValid = false; bool isValid = false;




+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Commands
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequireNsfwAttribute : PreconditionAttribute public class RequireNsfwAttribute : PreconditionAttribute
{ {
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services)
{ {
if (context.Channel.IsNsfw) if (context.Channel.IsNsfw)
return Task.FromResult(PreconditionResult.FromSuccess()); return Task.FromResult(PreconditionResult.FromSuccess());


+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs View File

@@ -11,7 +11,7 @@ namespace Discord.Commands
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequireOwnerAttribute : PreconditionAttribute public class RequireOwnerAttribute : PreconditionAttribute
{ {
public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services)
{ {
switch (context.Client.TokenType) switch (context.Client.TokenType)
{ {


+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs View File

@@ -43,7 +43,7 @@ namespace Discord.Commands
GuildPermission = null; GuildPermission = null;
} }
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, OverloadInfo overload, IServiceProvider services)
{ {
var guildUser = context.User as IGuildUser; var guildUser = context.User as IGuildUser;




+ 16
- 59
src/Discord.Net.Commands/Builders/CommandBuilder.cs View File

@@ -8,22 +8,17 @@ namespace Discord.Commands.Builders
{ {
public class CommandBuilder public class CommandBuilder
{ {
private readonly List<PreconditionAttribute> _preconditions;
private readonly List<ParameterBuilder> _parameters;
private readonly List<OverloadBuilder> _overloads;
private readonly List<string> _aliases; private readonly List<string> _aliases;


public ModuleBuilder Module { get; } public ModuleBuilder Module { get; }
internal Func<ICommandContext, object[], IServiceProvider, Task> Callback { get; set; }


public string Name { get; set; } public string Name { get; set; }
public string Summary { get; set; } public string Summary { get; set; }
public string Remarks { get; set; } public string Remarks { get; set; }
public string PrimaryAlias { get; set; } public string PrimaryAlias { get; set; }
public RunMode RunMode { get; set; }
public int Priority { get; set; }


public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions;
public IReadOnlyList<ParameterBuilder> Parameters => _parameters;
public IReadOnlyList<OverloadBuilder> Overloads => _overloads;
public IReadOnlyList<string> Aliases => _aliases; public IReadOnlyList<string> Aliases => _aliases;


//Automatic //Automatic
@@ -31,20 +26,23 @@ namespace Discord.Commands.Builders
{ {
Module = module; Module = module;


_preconditions = new List<PreconditionAttribute>();
_parameters = new List<ParameterBuilder>();
_overloads = new List<OverloadBuilder>();
_aliases = new List<string>(); _aliases = new List<string>();
} }
//User-defined //User-defined
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, Task> callback)
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Action<OverloadBuilder> defaultOverloadBuilder)
: this(module) : this(module)
{ {
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias));
Discord.Preconditions.NotNull(callback, nameof(callback));
Discord.Preconditions.NotNull(defaultOverloadBuilder, nameof(defaultOverloadBuilder));


Callback = callback;
PrimaryAlias = primaryAlias; PrimaryAlias = primaryAlias;
_aliases.Add(primaryAlias); _aliases.Add(primaryAlias);

var defaultOverload = new OverloadBuilder(this);
defaultOverloadBuilder(defaultOverload);

_overloads.Add(defaultOverload);
} }


public CommandBuilder WithName(string name) public CommandBuilder WithName(string name)
@@ -62,16 +60,6 @@ namespace Discord.Commands.Builders
Remarks = remarks; Remarks = remarks;
return this; return this;
} }
public CommandBuilder WithRunMode(RunMode runMode)
{
RunMode = runMode;
return this;
}
public CommandBuilder WithPriority(int priority)
{
Priority = priority;
return this;
}


public CommandBuilder AddAliases(params string[] aliases) public CommandBuilder AddAliases(params string[] aliases)
{ {
@@ -83,30 +71,12 @@ namespace Discord.Commands.Builders
} }
return this; return this;
} }
public CommandBuilder AddPrecondition(PreconditionAttribute precondition)
{
_preconditions.Add(precondition);
return this;
}
public CommandBuilder AddParameter<T>(string name, Action<ParameterBuilder> createFunc)
{
var param = new ParameterBuilder(this, name, typeof(T));
createFunc(param);
_parameters.Add(param);
return this;
}
public CommandBuilder AddParameter(string name, Type type, Action<ParameterBuilder> createFunc)
{
var param = new ParameterBuilder(this, name, type);
createFunc(param);
_parameters.Add(param);
return this;
}
internal CommandBuilder AddParameter(Action<ParameterBuilder> createFunc)

public CommandBuilder AddOverload(Action<OverloadBuilder> overloadBuilder)
{ {
var param = new ParameterBuilder(this);
createFunc(param);
_parameters.Add(param);
var overload = new OverloadBuilder(this);
overloadBuilder(overload);
_overloads.Add(overload);
return this; return this;
} }


@@ -116,20 +86,7 @@ namespace Discord.Commands.Builders
if (Name == null) if (Name == null)
Name = PrimaryAlias; Name = PrimaryAlias;


if (_parameters.Count > 0)
{
var lastParam = _parameters[_parameters.Count - 1];

var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple);
if ((firstMultipleParam != null) && (firstMultipleParam != lastParam))
throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag.");
var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder);
if ((firstRemainderParam != null) && (firstRemainderParam != lastParam))
throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag.");
}

return new CommandInfo(this, info, service); return new CommandInfo(this, info, service);
} }
} }
}
}

+ 2
- 2
src/Discord.Net.Commands/Builders/ModuleBuilder.cs View File

@@ -74,9 +74,9 @@ namespace Discord.Commands.Builders
_preconditions.Add(precondition); _preconditions.Add(precondition);
return this; return this;
} }
public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IServiceProvider, Task> callback, Action<CommandBuilder> createFunc)
public ModuleBuilder AddCommand(string primaryAlias, Action<OverloadBuilder> defaultOverloadBuilder, Action<CommandBuilder> createFunc)
{ {
var builder = new CommandBuilder(this, primaryAlias, callback);
var builder = new CommandBuilder(this, primaryAlias, defaultOverloadBuilder);
createFunc(builder); createFunc(builder);
_commands.Add(builder); _commands.Add(builder);
return this; return this;


+ 79
- 40
src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs View File

@@ -113,45 +113,78 @@ namespace Discord.Commands


var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x));


foreach (var method in validCommands)
var groupedCommands = validCommands.GroupBy(x => x.GetCustomAttribute<CommandAttribute>().Text);
foreach (var overloads in groupedCommands)
{ {
builder.AddCommand((command) => builder.AddCommand((command) =>
{ {
BuildCommand(command, typeInfo, method, service);
string firstName = null;

foreach (var method in overloads)
{
if (firstName == null)
firstName = method.Name;

command.AddOverload((overload) =>
{
BuildOverload(overload, typeInfo, method, service);
});
}

var allAttributes = overloads.SelectMany(x => x.GetCustomAttributes());
BuildCommand(command, firstName, allAttributes, service);
}); });
} }
} }


private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service)
private static void BuildCommand(CommandBuilder builder, string defaultName, IEnumerable<Attribute> attributes, CommandService service)
{ {
var attributes = method.GetCustomAttributes();
foreach (var attribute in attributes) foreach (var attribute in attributes)
{ {
// TODO: C#7 type switch
if (attribute is CommandAttribute)
switch (attribute)
{ {
var cmdAttr = attribute as CommandAttribute;
builder.AddAliases(cmdAttr.Text);
builder.RunMode = cmdAttr.RunMode;
builder.Name = builder.Name ?? cmdAttr.Text;
case CommandAttribute command:
builder.AddAliases(command.Text);
builder.Name = builder.Name ?? command.Text;
break;
case NameAttribute name:
builder.Name = name.Text;
break;
case SummaryAttribute summary:
builder.Summary = summary.Text;
break;
case RemarksAttribute remarks:
builder.Remarks = remarks.Text;
break;
case AliasAttribute alias:
builder.AddAliases(alias.Aliases);
break;
} }
else if (attribute is NameAttribute)
builder.Name = (attribute as NameAttribute).Text;
else if (attribute is PriorityAttribute)
builder.Priority = (attribute as PriorityAttribute).Priority;
else if (attribute is SummaryAttribute)
builder.Summary = (attribute as SummaryAttribute).Text;
else if (attribute is RemarksAttribute)
builder.Remarks = (attribute as RemarksAttribute).Text;
else if (attribute is AliasAttribute)
builder.AddAliases((attribute as AliasAttribute).Aliases);
else if (attribute is PreconditionAttribute)
builder.AddPrecondition(attribute as PreconditionAttribute);
} }


if (builder.Name == null) if (builder.Name == null)
builder.Name = method.Name;
builder.Name = defaultName;
}

private static void BuildOverload(OverloadBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service)
{
var attributes = method.GetCustomAttributes();

foreach (var attribute in attributes)
{
switch (attribute)
{
case CommandAttribute command:
builder.RunMode = command.RunMode;
break;
case PriorityAttribute priority:
builder.Priority = priority.Priority;
break;
case PreconditionAttribute precondition:
builder.AddPrecondition(precondition);
break;
}
}


var parameters = method.GetParameters(); var parameters = method.GetParameters();
int pos = 0, count = parameters.Length; int pos = 0, count = parameters.Length;
@@ -196,23 +229,29 @@ namespace Discord.Commands
foreach (var attribute in attributes) foreach (var attribute in attributes)
{ {
// TODO: C#7 type switch // TODO: C#7 type switch
if (attribute is SummaryAttribute)
builder.Summary = (attribute as SummaryAttribute).Text;
else if (attribute is OverrideTypeReaderAttribute)
builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader);
else if (attribute is ParameterPreconditionAttribute)
builder.AddPrecondition(attribute as ParameterPreconditionAttribute);
else if (attribute is ParamArrayAttribute)
{
builder.IsMultiple = true;
paramType = paramType.GetElementType();
}
else if (attribute is RemainderAttribute)
switch (attribute)
{ {
if (position != count-1)
throw new InvalidOperationException("Remainder parameters must be the last parameter in a command.");
builder.IsRemainder = true;
case SummaryAttribute summary:
builder.Summary = summary.Text;
break;
case OverrideTypeReaderAttribute typeReader:
builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader);
break;
case ParameterPreconditionAttribute precondition:
builder.AddPrecondition(precondition);
break;
case ParamArrayAttribute paramArray:
if (position != count - 1)
throw new InvalidOperationException("Params parameters must be the last parameter in a command.");
builder.IsMultiple = true;
paramType = paramType.GetElementType();
break;
case RemainderAttribute remainder:
if (position != count - 1)
throw new InvalidOperationException("Remainder parameters must be the last parameter in a command.");

builder.IsRemainder = true;
break;
} }
} }




+ 91
- 0
src/Discord.Net.Commands/Builders/OverloadBuilder.cs View File

@@ -0,0 +1,91 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Diagnostics;

using CommandCallback = System.Func<Discord.Commands.ICommandContext, object[], System.IServiceProvider, System.Threading.Tasks.Task>;

namespace Discord.Commands.Builders
{
public class OverloadBuilder
{
private readonly List<PreconditionAttribute> _preconditions;
private readonly List<ParameterBuilder> _parameters;

public CommandBuilder Command { get; }
public CommandCallback Callback { get; set; }

public RunMode RunMode { get; set; }
public int Priority { get; set; }

public IReadOnlyList<PreconditionAttribute> Preconditions => _preconditions;
public IReadOnlyList<ParameterBuilder> Parameters => _parameters;

internal OverloadBuilder(CommandBuilder command)
{
Command = command;

_preconditions = new List<PreconditionAttribute>();
_parameters = new List<ParameterBuilder>();
}

public OverloadBuilder WithRunMode(RunMode runMode)
{
RunMode = runMode;
return this;
}

public OverloadBuilder WithPriority(int priority)
{
Priority = priority;
return this;
}

public OverloadBuilder WithCallback(CommandCallback callback)
{
Callback = callback;
return this;
}

public OverloadBuilder AddPrecondition(PreconditionAttribute precondition)
{
_preconditions.Add(precondition);
return this;
}
internal OverloadBuilder AddParameter(Action<ParameterBuilder> createFunc)
{
var param = new ParameterBuilder(this);
createFunc(param);
_parameters.Add(param);
return this;
}
public OverloadBuilder AddParameter(string name, Type type, Action<ParameterBuilder> createFunc)
{
var param = new ParameterBuilder(this, name, type);
createFunc(param);
_parameters.Add(param);
return this;
}

internal OverloadInfo Build(CommandInfo info, CommandService service)
{
Discord.Preconditions.NotNull(Callback, nameof(Callback));

if (_parameters.Count > 0)
{
var lastParam = _parameters[_parameters.Count - 1];

var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple);
if ((firstMultipleParam != null) && (firstMultipleParam != lastParam))
throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag.");

var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder);
if ((firstRemainderParam != null) && (firstRemainderParam != lastParam))
throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag.");
}

return new OverloadInfo(this, info, service);
}
}
}

+ 7
- 5
src/Discord.Net.Commands/Builders/ParameterBuilder.cs View File

@@ -11,6 +11,7 @@ namespace Discord.Commands.Builders
private readonly List<ParameterPreconditionAttribute> _preconditions; private readonly List<ParameterPreconditionAttribute> _preconditions;


public CommandBuilder Command { get; } public CommandBuilder Command { get; }
public OverloadBuilder Overload { get; }
public string Name { get; internal set; } public string Name { get; internal set; }
public Type ParameterType { get; internal set; } public Type ParameterType { get; internal set; }


@@ -24,15 +25,16 @@ namespace Discord.Commands.Builders
public IReadOnlyList<ParameterPreconditionAttribute> Preconditions => _preconditions; public IReadOnlyList<ParameterPreconditionAttribute> Preconditions => _preconditions;


//Automatic //Automatic
internal ParameterBuilder(CommandBuilder command)
internal ParameterBuilder(OverloadBuilder overload)
{ {
_preconditions = new List<ParameterPreconditionAttribute>(); _preconditions = new List<ParameterPreconditionAttribute>();


Command = command;
Command = overload.Command;
Overload = overload;
} }
//User-defined //User-defined
internal ParameterBuilder(CommandBuilder command, string name, Type type)
: this(command)
internal ParameterBuilder(OverloadBuilder overload, string name, Type type)
: this(overload)
{ {
Discord.Preconditions.NotNull(name, nameof(name)); Discord.Preconditions.NotNull(name, nameof(name));


@@ -90,7 +92,7 @@ namespace Discord.Commands.Builders
return this; return this;
} }


internal ParameterInfo Build(CommandInfo info)
internal ParameterInfo Build(OverloadInfo info)
{ {
if (TypeReader == null) if (TypeReader == null)
throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified"); throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified");


+ 5
- 3
src/Discord.Net.Commands/CommandException.cs View File

@@ -5,12 +5,14 @@ namespace Discord.Commands
public class CommandException : Exception public class CommandException : Exception
{ {
public CommandInfo Command { get; } public CommandInfo Command { get; }
public OverloadInfo Overload { get; }
public ICommandContext Context { get; } public ICommandContext Context { get; }


public CommandException(CommandInfo command, ICommandContext context, Exception ex)
: base($"Error occurred executing {command.GetLogText(context)}.", ex)
public CommandException(OverloadInfo overload, ICommandContext context, Exception ex)
: base($"Error occurred executing {overload.GetLogText(context)}.", ex)
{ {
Command = command;
Overload = overload;
Command = overload.Command;
Context = context; Context = context;
} }
} }


+ 0
- 9
src/Discord.Net.Commands/CommandMatch.cs View File

@@ -15,14 +15,5 @@ namespace Discord.Commands
Command = command; Command = command;
Alias = alias; Alias = alias;
} }

public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
=> Command.CheckPreconditionsAsync(context, services);
public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null)
=> Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult);
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
=> Command.ExecuteAsync(context, argList, paramList, services);
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
=> Command.ExecuteAsync(context, parseResult, services);
} }
} }

+ 5
- 5
src/Discord.Net.Commands/CommandParser.cs View File

@@ -13,7 +13,7 @@ namespace Discord.Commands
QuotedParameter QuotedParameter
} }
public static async Task<ParseResult> ParseArgs(CommandInfo command, ICommandContext context, string input, int startPos)
public static async Task<ParseResult> ParseArgs(OverloadInfo overload, ICommandContext context, string input, int startPos)
{ {
ParameterInfo curParam = null; ParameterInfo curParam = null;
StringBuilder argBuilder = new StringBuilder(input.Length); StringBuilder argBuilder = new StringBuilder(input.Length);
@@ -66,7 +66,7 @@ namespace Discord.Commands
else else
{ {
if (curParam == null) if (curParam == null)
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null;
curParam = overload.Parameters.Count > argList.Count ? overload.Parameters[argList.Count] : null;


if (curParam != null && curParam.IsRemainder) if (curParam != null && curParam.IsRemainder)
{ {
@@ -145,9 +145,9 @@ namespace Discord.Commands
return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete"); return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete");
//Add missing optionals //Add missing optionals
for (int i = argList.Count; i < command.Parameters.Count; i++)
for (int i = argList.Count; i < overload.Parameters.Count; i++)
{ {
var param = command.Parameters[i];
var param = overload.Parameters[i];
if (param.IsMultiple) if (param.IsMultiple)
continue; continue;
if (!param.IsOptional) if (!param.IsOptional)
@@ -155,7 +155,7 @@ namespace Discord.Commands
argList.Add(TypeReaderResult.FromSuccess(param.DefaultValue)); argList.Add(TypeReaderResult.FromSuccess(param.DefaultValue));
} }
return ParseResult.FromSuccess(argList.ToImmutable(), paramList.ToImmutable());
return ParseResult.FromSuccess(overload, argList.ToImmutable(), paramList.ToImmutable());
} }
} }
} }

+ 63
- 30
src/Discord.Net.Commands/CommandService.cs View File

@@ -33,7 +33,7 @@ namespace Discord.Commands


public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x); public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x);
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands); public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands);
public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new {y.Key, y.Value})).ToLookup(x => x.Key, x => x.Value);
public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value);


public CommandService() : this(new CommandServiceConfig()) { } public CommandService() : this(new CommandServiceConfig()) { }
public CommandService(CommandServiceConfig config) public CommandService(CommandServiceConfig config)
@@ -59,6 +59,13 @@ namespace Discord.Commands
foreach (var type in PrimitiveParsers.SupportedTypes) foreach (var type in PrimitiveParsers.SupportedTypes)
_defaultTypeReaders[type] = PrimitiveTypeReader.Create(type); _defaultTypeReaders[type] = PrimitiveTypeReader.Create(type);


_defaultTypeReaders[typeof(string)] = new PrimitiveTypeReader<string>(0,
(string x, out string y) =>
{
y = x;
return true;
});

var entityTypeReaders = ImmutableList.CreateBuilder<Tuple<Type, Type>>(); var entityTypeReaders = ImmutableList.CreateBuilder<Tuple<Type, Type>>();
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IMessage), typeof(MessageTypeReader<>))); entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IMessage), typeof(MessageTypeReader<>)));
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IChannel), typeof(ChannelTypeReader<>))); entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IChannel), typeof(ChannelTypeReader<>)));
@@ -196,7 +203,7 @@ namespace Discord.Commands
} }
public void AddTypeReader(Type type, TypeReader reader) public void AddTypeReader(Type type, TypeReader reader)
{ {
var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary<Type, TypeReader>());
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
readers[reader.GetType()] = reader; readers[reader.GetType()] = reader;
} }
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type) internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
@@ -235,13 +242,13 @@ namespace Discord.Commands
} }


//Execution //Execution
public SearchResult Search(ICommandContext context, int argPos)
public SearchResult Search(ICommandContext context, int argPos)
=> Search(context, context.Message.Content.Substring(argPos)); => Search(context, context.Message.Content.Substring(argPos));
public SearchResult Search(ICommandContext context, string input) public SearchResult Search(ICommandContext context, string input)
{ {
string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); string searchInput = _caseSensitive ? input : input.ToLowerInvariant();
var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray();
var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Overloads.Average(y => y.Priority)).ToImmutableArray();
if (matches.Length > 0) if (matches.Length > 0)
return SearchResult.FromSuccess(input, matches); return SearchResult.FromSuccess(input, matches);
else else
@@ -261,41 +268,67 @@ namespace Discord.Commands
var commands = searchResult.Commands; var commands = searchResult.Commands;
for (int i = 0; i < commands.Count; i++) for (int i = 0; i < commands.Count; i++)
{ {
var preconditionResult = await commands[i].CheckPreconditionsAsync(context, services).ConfigureAwait(false);
if (!preconditionResult.IsSuccess)
var command = commands[i].Command;
var overloads = command.Overloads.OrderByDescending(x => x.Priority).ToImmutableArray();

var preconditionResult = PreconditionResult.FromSuccess();
for (int j = 0; j < overloads.Length; j++)
{
preconditionResult = await overloads[j].CheckPreconditionsAsync(context, services).ConfigureAwait(false);
if (!preconditionResult.IsSuccess)
{
if (i == commands.Count && j == overloads.Length)
return preconditionResult;
else
continue;
}
}

var rawParseResults = new List<ParseResult>();
foreach (var overload in overloads)
{ {
if (commands.Count == 1)
return preconditionResult;
else
continue;
rawParseResults.Add(await overload.ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false));
} }


var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false);
if (!parseResult.IsSuccess)
//order by average score
var orderedParseResults = rawParseResults.OrderByDescending(
x => !x.IsSuccess ? 0 :
(x.ArgValues.Count > 0 ? x.ArgValues.Average(y => y.Values.Max(z => z.Score)) : 0) +
(x.ParamValues.Count > 0 ? x.ParamValues.Average(y => y.Values.Max(z => z.Score)) : 0));

var parseResults = orderedParseResults.ToImmutableArray();

for (int j = 0; j < parseResults.Length; j++)
{ {
if (parseResult.Error == CommandError.MultipleMatches)
var parseResult = parseResults[j];
var overload = parseResult.Overload;

if (!parseResult.IsSuccess)
{ {
IReadOnlyList<TypeReaderValue> argList, paramList;
switch (multiMatchHandling)
if (parseResult.Error == CommandError.MultipleMatches)
{ {
case MultiMatchHandling.Best:
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
parseResult = ParseResult.FromSuccess(argList, paramList);
break;
IReadOnlyList<TypeReaderValue> argList, paramList;
switch (multiMatchHandling)
{
case MultiMatchHandling.Best:
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
parseResult = ParseResult.FromSuccess(overload, argList, paramList);
break;
}
} }
}


if (!parseResult.IsSuccess)
{
if (commands.Count == 1)
return parseResult;
else
continue;
if (!parseResult.IsSuccess)
{
if (i == commands.Count && j == parseResults.Length)
return parseResult;
else
continue;
}
} }
}


return await commands[i].ExecuteAsync(context, parseResult, services).ConfigureAwait(false);
return await overload.ExecuteAsync(context, parseResult, services).ConfigureAwait(false);
}
} }


return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload.");


+ 4
- 180
src/Discord.Net.Commands/Info/CommandInfo.cs View File

@@ -15,34 +15,22 @@ namespace Discord.Commands
[DebuggerDisplay("{Name,nq}")] [DebuggerDisplay("{Name,nq}")]
public class CommandInfo public class CommandInfo
{ {
private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();

private readonly Func<ICommandContext, object[], IServiceProvider, Task> _action;

public ModuleInfo Module { get; } public ModuleInfo Module { get; }
public string Name { get; } public string Name { get; }
public string Summary { get; } public string Summary { get; }
public string Remarks { get; } public string Remarks { get; }
public int Priority { get; }
public bool HasVarArgs { get; }
public RunMode RunMode { get; }


public IReadOnlyList<string> Aliases { get; } public IReadOnlyList<string> Aliases { get; }
public IReadOnlyList<ParameterInfo> Parameters { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
public IReadOnlyList<OverloadInfo> Overloads { get; }


internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service)
{ {
Module = module; Module = module;
Name = builder.Name; Name = builder.Name;
Summary = builder.Summary; Summary = builder.Summary;
Remarks = builder.Remarks; Remarks = builder.Remarks;


RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode);
Priority = builder.Priority;
Aliases = module.Aliases Aliases = module.Aliases
.Permutate(builder.Aliases, (first, second) => .Permutate(builder.Aliases, (first, second) =>
{ {
@@ -56,171 +44,7 @@ namespace Discord.Commands
.Select(x => service._caseSensitive ? x : x.ToLowerInvariant()) .Select(x => service._caseSensitive ? x : x.ToLowerInvariant())
.ToImmutableArray(); .ToImmutableArray();


Preconditions = builder.Preconditions.ToImmutableArray();

Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray();
HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false;

_action = builder.Callback;
}

public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
{
services = services ?? EmptyServiceProvider.Instance;

foreach (PreconditionAttribute precondition in Module.Preconditions)
{
var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}

foreach (PreconditionAttribute precondition in Preconditions)
{
var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}

return PreconditionResult.FromSuccess();
}
public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult? preconditionResult = null)
{
if (!searchResult.IsSuccess)
return ParseResult.FromError(searchResult);
if (preconditionResult != null && !preconditionResult.Value.IsSuccess)
return ParseResult.FromError(preconditionResult.Value);
string input = searchResult.Text.Substring(startIndex);
return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false);
}

public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
{
if (!parseResult.IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult));

var argList = new object[parseResult.ArgValues.Count];
for (int i = 0; i < parseResult.ArgValues.Count; i++)
{
if (!parseResult.ArgValues[i].IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i]));
argList[i] = parseResult.ArgValues[i].Values.First().Value;
}
var paramList = new object[parseResult.ParamValues.Count];
for (int i = 0; i < parseResult.ParamValues.Count; i++)
{
if (!parseResult.ParamValues[i].IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i]));
paramList[i] = parseResult.ParamValues[i].Values.First().Value;
}

return ExecuteAsync(context, argList, paramList, services);
}
public async Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
{
services = services ?? EmptyServiceProvider.Instance;

try
{
object[] args = GenerateArgs(argList, paramList);

for (int position = 0; position < Parameters.Count; position++)
{
var parameter = Parameters[position];
var argument = args[position];
var result = await parameter.CheckPreconditionsAsync(context, argument, services).ConfigureAwait(false);
if (!result.IsSuccess)
return ExecuteResult.FromError(result);
}

switch (RunMode)
{
case RunMode.Sync: //Always sync
await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false);
break;
case RunMode.Async: //Always async
var t2 = Task.Run(async () =>
{
await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false);
});
break;
}
return ExecuteResult.FromSuccess();
}
catch (Exception ex)
{
return ExecuteResult.FromError(ex);
}
}

private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider services)
{
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
try
{
await _action(context, args, services).ConfigureAwait(false);
}
catch (Exception ex)
{
var originalEx = ex;
while (ex is TargetInvocationException) //Happens with void-returning commands
ex = ex.InnerException;

var wrappedEx = new CommandException(this, context, ex);
await Module.Service._cmdLogger.ErrorAsync(wrappedEx).ConfigureAwait(false);
if (Module.Service._throwOnError)
{
if (ex == originalEx)
throw;
else
ExceptionDispatchInfo.Capture(ex).Throw();
}
}
await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false);
}

private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList)
{
int argCount = Parameters.Count;
var array = new object[Parameters.Count];
if (HasVarArgs)
argCount--;

int i = 0;
foreach (var arg in argList)
{
if (i == argCount)
throw new InvalidOperationException("Command was invoked with too many parameters");
array[i++] = arg;
}
if (i < argCount)
throw new InvalidOperationException("Command was invoked with too few parameters");

if (HasVarArgs)
{
var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t =>
{
var method = _convertParamsMethod.MakeGenericMethod(t);
return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>));
});
array[i] = func(paramsList);
}

return array;
}

private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList)
=> paramsList.Cast<T>().ToArray();

internal string GetLogText(ICommandContext context)
{
if (context.Guild != null)
return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}";
else
return $"\"{Name}\" for {context.User} in {context.Channel}";
Overloads = builder.Overloads.Select(x => x.Build(this, service)).ToImmutableArray();
} }
} }
}
}

+ 207
- 0
src/Discord.Net.Commands/Info/OverloadInfo.cs View File

@@ -0,0 +1,207 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Reflection;
using System.Diagnostics;

using Discord.Commands.Builders;
using System.Runtime.ExceptionServices;

namespace Discord.Commands
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class OverloadInfo
{
private static readonly MethodInfo _convertParamsMethod = typeof(OverloadInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();

private readonly Func<ICommandContext, object[], IServiceProvider, Task> _action;

public CommandInfo Command { get; }
public int Priority { get; }
public bool HasVarArgs { get; }
public RunMode RunMode { get; }

public IReadOnlyList<ParameterInfo> Parameters { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }

internal OverloadInfo(OverloadBuilder builder, CommandInfo command, CommandService service)
{
Command = command;

RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode);
Priority = builder.Priority;

Preconditions = builder.Preconditions.ToImmutableArray();

Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray();
HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false;

_action = builder.Callback;
}

public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
{
if (services == null)
services = EmptyServiceProvider.Instance;

foreach (PreconditionAttribute precondition in Command.Module.Preconditions)
{
var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}

foreach (PreconditionAttribute precondition in Preconditions)
{
var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}

return PreconditionResult.FromSuccess();
}

public async Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null)
{
if (!searchResult.IsSuccess)
return ParseResult.FromError(searchResult);
if (preconditionResult != null && !preconditionResult.Value.IsSuccess)
return ParseResult.FromError(preconditionResult.Value);

string input = searchResult.Text;
var matchingAliases = Command.Aliases.Where(alias => input.StartsWith(alias));

string matchingAlias = "";
foreach (string alias in matchingAliases)
{
if (alias.Length > matchingAlias.Length)
matchingAlias = alias;
}

input = input.Substring(matchingAlias.Length);

return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false);
}

public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
{
if (!parseResult.IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult));

var argList = new object[parseResult.ArgValues.Count];
for (int i = 0; i < parseResult.ArgValues.Count; i++)
{
if (!parseResult.ArgValues[i].IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i]));
argList[i] = parseResult.ArgValues[i].Values.First().Value;
}

var paramList = new object[parseResult.ParamValues.Count];
for (int i = 0; i < parseResult.ParamValues.Count; i++)
{
if (!parseResult.ParamValues[i].IsSuccess)
return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i]));
paramList[i] = parseResult.ParamValues[i].Values.First().Value;
}

return ExecuteAsync(context, argList, paramList, services);
}
public async Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
{
if (services == null)
services = EmptyServiceProvider.Instance;

try
{
var args = GenerateArgs(argList, paramList);
switch (RunMode)
{
case RunMode.Sync: //Always sync
await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false);
break;
case RunMode.Async: //Always async
var t2 = Task.Run(() => ExecuteAsyncInternal(context, args, services));
break;
}
return ExecuteResult.FromSuccess();
}
catch (Exception ex)
{
return ExecuteResult.FromError(ex);
}
}

private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider map)
{
await Command.Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
try
{
await _action(context, args, map).ConfigureAwait(false);
}
catch (Exception ex)
{
var originalEx = ex;
while (ex is TargetInvocationException) //Happens with void-returning commands
ex = ex.InnerException;

var wrappedEx = new CommandException(this, context, ex);
await Command.Module.Service._cmdLogger.ErrorAsync(wrappedEx).ConfigureAwait(false);
if (Command.Module.Service._throwOnError)
{
if (ex == originalEx)
throw;
else
ExceptionDispatchInfo.Capture(ex).Throw();
}
}
await Command.Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false);
}

internal string GetLogText(ICommandContext context)
{
if (context.Guild != null)
return $"\"{Command.Name}\" for {context.User} in {context.Guild}/{context.Channel}";
else
return $"\"{Command.Name}\" for {context.User} in {context.Channel}";
}

private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList)
{
int argCount = Parameters.Count;
var array = new object[Parameters.Count];
if (HasVarArgs)
argCount--;

int i = 0;
foreach (var arg in argList)
{
if (i == argCount)
throw new InvalidOperationException("Command was invoked with too many parameters");
array[i++] = arg;
}
if (i < argCount)
throw new InvalidOperationException("Command was invoked with too few parameters");

if (HasVarArgs)
{
var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t =>
{
var method = _convertParamsMethod.MakeGenericMethod(t);
return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>));
});
array[i] = func(paramsList);
}

return array;
}

private string DebuggerDisplay => $"{Command.Name} ({Priority}, {RunMode})";

private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList)
=> paramsList.Cast<T>().ToArray();
}
}

+ 4
- 2
src/Discord.Net.Commands/Info/ParameterInfo.cs View File

@@ -12,6 +12,7 @@ namespace Discord.Commands
private readonly TypeReader _reader; private readonly TypeReader _reader;


public CommandInfo Command { get; } public CommandInfo Command { get; }
public OverloadInfo Overload { get; }
public string Name { get; } public string Name { get; }
public string Summary { get; } public string Summary { get; }
public bool IsOptional { get; } public bool IsOptional { get; }
@@ -22,9 +23,10 @@ namespace Discord.Commands


public IReadOnlyList<ParameterPreconditionAttribute> Preconditions { get; } public IReadOnlyList<ParameterPreconditionAttribute> Preconditions { get; }


internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service)
internal ParameterInfo(ParameterBuilder builder, OverloadInfo overload, CommandService service)
{ {
Command = command;
Command = overload.Command;
Overload = overload;


Name = builder.Name; Name = builder.Name;
Summary = builder.Summary; Summary = builder.Summary;


+ 0
- 5
src/Discord.Net.Commands/PrimitiveParsers.cs View File

@@ -31,11 +31,6 @@ namespace Discord.Commands
parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate<DateTimeOffset>)DateTimeOffset.TryParse; parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate<DateTimeOffset>)DateTimeOffset.TryParse;
parserBuilder[typeof(TimeSpan)] = (TryParseDelegate<TimeSpan>)TimeSpan.TryParse; parserBuilder[typeof(TimeSpan)] = (TryParseDelegate<TimeSpan>)TimeSpan.TryParse;
parserBuilder[typeof(char)] = (TryParseDelegate<char>)char.TryParse; parserBuilder[typeof(char)] = (TryParseDelegate<char>)char.TryParse;
parserBuilder[typeof(string)] = (TryParseDelegate<string>)delegate (string str, out string value)
{
value = str;
return true;
};
return parserBuilder.ToImmutable(); return parserBuilder.ToImmutable();
} }




+ 8
- 2
src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs View File

@@ -15,17 +15,23 @@ namespace Discord.Commands
internal class PrimitiveTypeReader<T> : TypeReader internal class PrimitiveTypeReader<T> : TypeReader
{ {
private readonly TryParseDelegate<T> _tryParse; private readonly TryParseDelegate<T> _tryParse;
private readonly float _score;


public PrimitiveTypeReader() public PrimitiveTypeReader()
: this(1, PrimitiveParsers.Get<T>())
{ }

public PrimitiveTypeReader(float score, TryParseDelegate<T> tryParse)
{ {
_tryParse = PrimitiveParsers.Get<T>();
_tryParse = tryParse;
_score = score;
} }


public override Task<TypeReaderResult> Read(ICommandContext context, string input) public override Task<TypeReaderResult> Read(ICommandContext context, string input)
{ {
T value; T value;
if (_tryParse(input, out value)) if (_tryParse(input, out value))
return Task.FromResult(TypeReaderResult.FromSuccess(value));
return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score)));
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}")); return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}"));
} }
} }


+ 12
- 9
src/Discord.Net.Commands/Results/ParseResult.cs View File

@@ -9,34 +9,37 @@ namespace Discord.Commands
public IReadOnlyList<TypeReaderResult> ArgValues { get; } public IReadOnlyList<TypeReaderResult> ArgValues { get; }
public IReadOnlyList<TypeReaderResult> ParamValues { get; } public IReadOnlyList<TypeReaderResult> ParamValues { get; }


public OverloadInfo Overload { get; }

public CommandError? Error { get; } public CommandError? Error { get; }
public string ErrorReason { get; } public string ErrorReason { get; }


public bool IsSuccess => !Error.HasValue; public bool IsSuccess => !Error.HasValue;


private ParseResult(IReadOnlyList<TypeReaderResult> argValues, IReadOnlyList<TypeReaderResult> paramValues, CommandError? error, string errorReason)
private ParseResult(OverloadInfo overload, IReadOnlyList<TypeReaderResult> argValues, IReadOnlyList<TypeReaderResult> paramValues, CommandError? error, string errorReason)
{ {
Overload = overload;
ArgValues = argValues; ArgValues = argValues;
ParamValues = paramValues; ParamValues = paramValues;
Error = error; Error = error;
ErrorReason = errorReason; ErrorReason = errorReason;
} }


public static ParseResult FromSuccess(IReadOnlyList<TypeReaderResult> argValues, IReadOnlyList<TypeReaderResult> paramValues)
public static ParseResult FromSuccess(OverloadInfo overload, IReadOnlyList<TypeReaderResult> argValues, IReadOnlyList<TypeReaderResult> paramValues)
{ {
for (int i = 0; i < argValues.Count; i++) for (int i = 0; i < argValues.Count; i++)
{ {
if (argValues[i].Values.Count > 1) if (argValues[i].Values.Count > 1)
return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found.");
return new ParseResult(overload, argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found.");
} }
for (int i = 0; i < paramValues.Count; i++) for (int i = 0; i < paramValues.Count; i++)
{ {
if (paramValues[i].Values.Count > 1) if (paramValues[i].Values.Count > 1)
return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found.");
return new ParseResult(overload, argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found.");
} }
return new ParseResult(argValues, paramValues, null, null);
return new ParseResult(overload, argValues, paramValues, null, null);
} }
public static ParseResult FromSuccess(IReadOnlyList<TypeReaderValue> argValues, IReadOnlyList<TypeReaderValue> paramValues)
public static ParseResult FromSuccess(OverloadInfo overload, IReadOnlyList<TypeReaderValue> argValues, IReadOnlyList<TypeReaderValue> paramValues)
{ {
var argList = new TypeReaderResult[argValues.Count]; var argList = new TypeReaderResult[argValues.Count];
for (int i = 0; i < argValues.Count; i++) for (int i = 0; i < argValues.Count; i++)
@@ -48,13 +51,13 @@ namespace Discord.Commands
for (int i = 0; i < paramValues.Count; i++) for (int i = 0; i < paramValues.Count; i++)
paramList[i] = TypeReaderResult.FromSuccess(paramValues[i]); paramList[i] = TypeReaderResult.FromSuccess(paramValues[i]);
} }
return new ParseResult(argList, paramList, null, null);
return new ParseResult(overload, argList, paramList, null, null);
} }


public static ParseResult FromError(CommandError error, string reason) public static ParseResult FromError(CommandError error, string reason)
=> new ParseResult(null, null, error, reason);
=> new ParseResult(null, null, null, error, reason);
public static ParseResult FromError(IResult result) public static ParseResult FromError(IResult result)
=> new ParseResult(null, null, result.Error, result.ErrorReason);
=> new ParseResult(null, null, null, result.Error, result.ErrorReason);


public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
private string DebuggerDisplay => IsSuccess ? $"Success ({ArgValues.Count}{(ParamValues.Count > 0 ? $" +{ParamValues.Count} Values" : "")})" : $"{Error}: {ErrorReason}"; private string DebuggerDisplay => IsSuccess ? $"Success ({ArgValues.Count}{(ParamValues.Count > 0 ? $" +{ParamValues.Count} Values" : "")})" : $"{Error}: {ErrorReason}";


Loading…
Cancel
Save