Implemented command type readers, parser and service.
This commit is contained in:
@@ -6,13 +6,14 @@ namespace Discord.Commands
|
|||||||
public class CommandAttribute : Attribute
|
public class CommandAttribute : Attribute
|
||||||
{
|
{
|
||||||
public string Text { get; }
|
public string Text { get; }
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
public CommandAttribute(string name) : this(name, name) { }
|
public CommandAttribute()
|
||||||
public CommandAttribute(string text, string name)
|
|
||||||
{
|
{
|
||||||
Text = text.ToLowerInvariant();
|
Text = null;
|
||||||
Name = name;
|
}
|
||||||
|
public CommandAttribute(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,121 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
public class Command
|
public class Command
|
||||||
{
|
{
|
||||||
private Action<IMessage> _action;
|
private readonly object _instance;
|
||||||
|
private readonly Func<IMessage, IReadOnlyList<object>, Task> _action;
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
public string Text { get; }
|
public string Text { get; }
|
||||||
|
public Module Module { get; }
|
||||||
|
public IReadOnlyList<CommandParameter> Parameters { get; }
|
||||||
|
|
||||||
internal Command(CommandAttribute attribute, MethodInfo methodInfo)
|
internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo)
|
||||||
{
|
{
|
||||||
|
Module = module;
|
||||||
|
_instance = instance;
|
||||||
|
|
||||||
|
Name = methodInfo.Name;
|
||||||
|
Text = attribute.Text;
|
||||||
|
|
||||||
var description = methodInfo.GetCustomAttribute<DescriptionAttribute>();
|
var description = methodInfo.GetCustomAttribute<DescriptionAttribute>();
|
||||||
if (description != null)
|
if (description != null)
|
||||||
Description = description.Text;
|
Description = description.Text;
|
||||||
|
|
||||||
Name = attribute.Name;
|
Parameters = BuildParameters(methodInfo);
|
||||||
Text = attribute.Text;
|
_action = BuildAction(methodInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Invoke(IMessage msg)
|
public async Task<ParseResult> Parse(IMessage msg, SearchResult searchResult)
|
||||||
{
|
{
|
||||||
_action.Invoke(msg);
|
if (!searchResult.IsSuccess)
|
||||||
|
return ParseResult.FromError(searchResult);
|
||||||
|
|
||||||
|
return await CommandParser.ParseArgs(this, msg, searchResult.ArgText, 0).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public async Task<ExecuteResult> Execute(IMessage msg, ParseResult parseResult)
|
||||||
|
{
|
||||||
|
if (!parseResult.IsSuccess)
|
||||||
|
return ExecuteResult.FromError(parseResult);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _action.Invoke(msg, parseResult.Values);//Note: This code may need context
|
||||||
|
return ExecuteResult.FromSuccess();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return ExecuteResult.FromError(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildAction()
|
private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo)
|
||||||
{
|
{
|
||||||
_action = null;
|
var parameters = methodInfo.GetParameters();
|
||||||
//TODO: Implement
|
var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length - 1);
|
||||||
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
var parameter = parameters[i];
|
||||||
|
var type = parameter.ParameterType;
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
if (type != typeof(IMessage))
|
||||||
|
throw new InvalidOperationException("The first parameter of a command must be IMessage.");
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeInfo = type.GetTypeInfo();
|
||||||
|
if (typeInfo.IsEnum)
|
||||||
|
type = Enum.GetUnderlyingType(type);
|
||||||
|
|
||||||
|
var reader = Module.Service.GetTypeReader(type);
|
||||||
|
if (reader == null)
|
||||||
|
throw new InvalidOperationException($"This type ({type.FullName}) is not supported.");
|
||||||
|
|
||||||
|
bool isUnparsed = parameter.GetCustomAttribute<UnparsedAttribute>() != null;
|
||||||
|
if (isUnparsed)
|
||||||
|
{
|
||||||
|
if (type != typeof(string))
|
||||||
|
throw new InvalidOperationException("Unparsed parameters only support the string type.");
|
||||||
|
else if (i != parameters.Length - 1)
|
||||||
|
throw new InvalidOperationException("Unparsed parameters must be the last parameter in a command.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string name = parameter.Name;
|
||||||
|
string description = typeInfo.GetCustomAttribute<DescriptionAttribute>()?.Text;
|
||||||
|
bool isOptional = parameter.IsOptional;
|
||||||
|
object defaultValue = parameter.HasDefaultValue ? parameter.DefaultValue : null;
|
||||||
|
|
||||||
|
paramBuilder.Add(new CommandParameter(name, description, reader, isOptional, isUnparsed, defaultValue));
|
||||||
|
}
|
||||||
|
return paramBuilder.ToImmutable();
|
||||||
}
|
}
|
||||||
|
private Func<IMessage, IReadOnlyList<object>, Task> BuildAction(MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
//TODO: Temporary reflection hack. Lets build an actual expression tree here.
|
||||||
|
return (msg, args) =>
|
||||||
|
{
|
||||||
|
object[] newArgs = new object[args.Count + 1];
|
||||||
|
newArgs[0] = msg;
|
||||||
|
for (int i = 0; i < args.Count; i++)
|
||||||
|
newArgs[i + 1] = args[i];
|
||||||
|
var result = methodInfo.Invoke(_instance, newArgs);
|
||||||
|
return result as Task ?? Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Name;
|
||||||
|
private string DebuggerDisplay => $"{Module.Name}.{Name} ({Text})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/Discord.Net.Commands/CommandError.cs
Normal file
20
src/Discord.Net.Commands/CommandError.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
public enum CommandError
|
||||||
|
{
|
||||||
|
//Search
|
||||||
|
UnknownCommand,
|
||||||
|
|
||||||
|
//Parse
|
||||||
|
ParseFailed,
|
||||||
|
BadArgCount,
|
||||||
|
|
||||||
|
//Parse (Type Reader)
|
||||||
|
CastFailed,
|
||||||
|
ObjectNotFound,
|
||||||
|
MultipleMatches,
|
||||||
|
|
||||||
|
//Execute
|
||||||
|
Exception,
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/Discord.Net.Commands/CommandParameter.cs
Normal file
34
src/Discord.Net.Commands/CommandParameter.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
//TODO: Add support for Multiple
|
||||||
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
|
public class CommandParameter
|
||||||
|
{
|
||||||
|
private readonly TypeReader _reader;
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public bool IsOptional { get; }
|
||||||
|
public bool IsUnparsed { get; }
|
||||||
|
internal object DefaultValue { get; }
|
||||||
|
|
||||||
|
public CommandParameter(string name, string description, TypeReader reader, bool isOptional, bool isUnparsed, object defaultValue)
|
||||||
|
{
|
||||||
|
_reader = reader;
|
||||||
|
IsOptional = isOptional;
|
||||||
|
IsUnparsed = isUnparsed;
|
||||||
|
DefaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TypeReaderResult> Parse(IMessage context, string input)
|
||||||
|
{
|
||||||
|
return await _reader.Read(context, input).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Name;
|
||||||
|
private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsUnparsed ? " (Unparsed)" : "")}";
|
||||||
|
}
|
||||||
|
}
|
||||||
144
src/Discord.Net.Commands/CommandParser.cs
Normal file
144
src/Discord.Net.Commands/CommandParser.cs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
internal static class CommandParser
|
||||||
|
{
|
||||||
|
private enum ParserPart
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Parameter,
|
||||||
|
QuotedParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Check support for escaping
|
||||||
|
public static async Task<ParseResult> ParseArgs(Command command, IMessage context, string input, int startPos)
|
||||||
|
{
|
||||||
|
CommandParameter curParam = null;
|
||||||
|
StringBuilder argBuilder = new StringBuilder(input.Length);
|
||||||
|
int endPos = input.Length;
|
||||||
|
var curPart = ParserPart.None;
|
||||||
|
int lastArgEndPos = int.MinValue;
|
||||||
|
var argList = ImmutableArray.CreateBuilder<object>();
|
||||||
|
bool isEscaping = false;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
for (int curPos = startPos; curPos <= endPos; curPos++)
|
||||||
|
{
|
||||||
|
if (curPos < endPos)
|
||||||
|
c = input[curPos];
|
||||||
|
else
|
||||||
|
c = '\0';
|
||||||
|
|
||||||
|
//If this character is escaped, skip it
|
||||||
|
if (isEscaping)
|
||||||
|
{
|
||||||
|
if (curPos != endPos)
|
||||||
|
{
|
||||||
|
argBuilder.Append(c);
|
||||||
|
isEscaping = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Are we escaping the next character?
|
||||||
|
if (c == '\\')
|
||||||
|
{
|
||||||
|
isEscaping = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we're processing an unparsed parameter, ignore all other logic
|
||||||
|
if (curParam != null && curParam.IsUnparsed)
|
||||||
|
{
|
||||||
|
argBuilder.Append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we're not currently processing one, are we starting the next argument yet?
|
||||||
|
if (curPart == ParserPart.None)
|
||||||
|
{
|
||||||
|
if (char.IsWhiteSpace(c) || curPos == endPos)
|
||||||
|
continue; //Skip whitespace between arguments
|
||||||
|
else if (curPos == lastArgEndPos)
|
||||||
|
return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments.");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null;
|
||||||
|
if (curParam.IsUnparsed)
|
||||||
|
{
|
||||||
|
argBuilder.Append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '\"')
|
||||||
|
{
|
||||||
|
curPart = ParserPart.QuotedParameter;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
curPart = ParserPart.Parameter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Has this parameter ended yet?
|
||||||
|
string argString = null;
|
||||||
|
if (curPart == ParserPart.Parameter)
|
||||||
|
{
|
||||||
|
if (curPos == endPos || char.IsWhiteSpace(c))
|
||||||
|
{
|
||||||
|
argString = argBuilder.ToString();
|
||||||
|
lastArgEndPos = curPos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
argBuilder.Append(c);
|
||||||
|
}
|
||||||
|
else if (curPart == ParserPart.QuotedParameter)
|
||||||
|
{
|
||||||
|
if (c == '\"')
|
||||||
|
{
|
||||||
|
argString = argBuilder.ToString(); //Remove quotes
|
||||||
|
lastArgEndPos = curPos + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
argBuilder.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argString != null)
|
||||||
|
{
|
||||||
|
if (curParam == null)
|
||||||
|
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters.");
|
||||||
|
|
||||||
|
var typeReaderResult = await curParam.Parse(context, argString).ConfigureAwait(false);
|
||||||
|
if (!typeReaderResult.IsSuccess)
|
||||||
|
return ParseResult.FromError(typeReaderResult);
|
||||||
|
argList.Add(typeReaderResult.Value);
|
||||||
|
|
||||||
|
curParam = null;
|
||||||
|
curPart = ParserPart.None;
|
||||||
|
argBuilder.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curParam != null && curParam.IsUnparsed)
|
||||||
|
argList.Add(argBuilder.ToString());
|
||||||
|
|
||||||
|
if (isEscaping)
|
||||||
|
return ParseResult.FromError(CommandError.ParseFailed, "Input text may not end on an incomplete escape.");
|
||||||
|
if (curPart == ParserPart.QuotedParameter)
|
||||||
|
return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete");
|
||||||
|
|
||||||
|
if (argList.Count < command.Parameters.Count)
|
||||||
|
{
|
||||||
|
for (int i = argList.Count; i < command.Parameters.Count; i++)
|
||||||
|
{
|
||||||
|
var param = command.Parameters[i];
|
||||||
|
if (!param.IsOptional)
|
||||||
|
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too few parameters.");
|
||||||
|
argList.Add(param.DefaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult.FromSuccess(argList.ToImmutable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -14,6 +15,7 @@ namespace Discord.Commands
|
|||||||
private readonly SemaphoreSlim _moduleLock;
|
private readonly SemaphoreSlim _moduleLock;
|
||||||
private readonly ConcurrentDictionary<object, Module> _modules;
|
private readonly ConcurrentDictionary<object, Module> _modules;
|
||||||
private readonly ConcurrentDictionary<string, List<Command>> _map;
|
private readonly ConcurrentDictionary<string, List<Command>> _map;
|
||||||
|
private readonly Dictionary<Type, TypeReader> _typeReaders;
|
||||||
|
|
||||||
public IEnumerable<Module> Modules => _modules.Select(x => x.Value);
|
public IEnumerable<Module> Modules => _modules.Select(x => x.Value);
|
||||||
public IEnumerable<Command> Commands => _modules.SelectMany(x => x.Value.Commands);
|
public IEnumerable<Command> Commands => _modules.SelectMany(x => x.Value.Commands);
|
||||||
@@ -23,6 +25,113 @@ namespace Discord.Commands
|
|||||||
_moduleLock = new SemaphoreSlim(1, 1);
|
_moduleLock = new SemaphoreSlim(1, 1);
|
||||||
_modules = new ConcurrentDictionary<object, Module>();
|
_modules = new ConcurrentDictionary<object, Module>();
|
||||||
_map = new ConcurrentDictionary<string, List<Command>>();
|
_map = new ConcurrentDictionary<string, List<Command>>();
|
||||||
|
_typeReaders = new Dictionary<Type, TypeReader>
|
||||||
|
{
|
||||||
|
[typeof(string)] = new GenericTypeReader((m, s) => Task.FromResult(TypeReaderResult.FromSuccess(s))),
|
||||||
|
[typeof(byte)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
byte value;
|
||||||
|
if (byte.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse Byte"));
|
||||||
|
}),
|
||||||
|
[typeof(sbyte)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
sbyte value;
|
||||||
|
if (sbyte.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse SByte"));
|
||||||
|
}),
|
||||||
|
[typeof(ushort)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
ushort value;
|
||||||
|
if (ushort.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse UInt16"));
|
||||||
|
}),
|
||||||
|
[typeof(short)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
short value;
|
||||||
|
if (short.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse Int16"));
|
||||||
|
}),
|
||||||
|
[typeof(uint)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
uint value;
|
||||||
|
if (uint.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse UInt32"));
|
||||||
|
}),
|
||||||
|
[typeof(int)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
int value;
|
||||||
|
if (int.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse Int32"));
|
||||||
|
}),
|
||||||
|
[typeof(ulong)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
ulong value;
|
||||||
|
if (ulong.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse UInt64"));
|
||||||
|
}),
|
||||||
|
[typeof(long)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
long value;
|
||||||
|
if (long.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse Int64"));
|
||||||
|
}),
|
||||||
|
[typeof(float)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
if (float.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse Single"));
|
||||||
|
}),
|
||||||
|
[typeof(double)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
double value;
|
||||||
|
if (double.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse Double"));
|
||||||
|
}),
|
||||||
|
[typeof(decimal)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
decimal value;
|
||||||
|
if (decimal.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse Decimal"));
|
||||||
|
}),
|
||||||
|
[typeof(DateTime)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
DateTime value;
|
||||||
|
if (DateTime.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse DateTime"));
|
||||||
|
}),
|
||||||
|
[typeof(DateTimeOffset)] = new GenericTypeReader((m, s) =>
|
||||||
|
{
|
||||||
|
DateTimeOffset value;
|
||||||
|
if (DateTimeOffset.TryParse(s, out value)) return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse DateTimeOffset"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
[typeof(IMessage)] = new MessageTypeReader(),
|
||||||
|
[typeof(IChannel)] = new ChannelTypeReader<IChannel>(),
|
||||||
|
[typeof(IGuildChannel)] = new ChannelTypeReader<IGuildChannel>(),
|
||||||
|
[typeof(ITextChannel)] = new ChannelTypeReader<ITextChannel>(),
|
||||||
|
[typeof(IVoiceChannel)] = new ChannelTypeReader<IVoiceChannel>(),
|
||||||
|
[typeof(IRole)] = new RoleTypeReader(),
|
||||||
|
[typeof(IUser)] = new UserTypeReader<IUser>(),
|
||||||
|
[typeof(IGuildUser)] = new UserTypeReader<IGuildUser>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTypeReader<T>(TypeReader reader)
|
||||||
|
{
|
||||||
|
_typeReaders[typeof(T)] = reader;
|
||||||
|
}
|
||||||
|
public void AddTypeReader(Type type, TypeReader reader)
|
||||||
|
{
|
||||||
|
_typeReaders[type] = reader;
|
||||||
|
}
|
||||||
|
internal TypeReader GetTypeReader(Type type)
|
||||||
|
{
|
||||||
|
TypeReader reader;
|
||||||
|
if (_typeReaders.TryGetValue(type, out reader))
|
||||||
|
return reader;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Module> Load(object module)
|
public async Task<Module> Load(object module)
|
||||||
@@ -46,7 +155,7 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
private Module LoadInternal(object module, TypeInfo typeInfo)
|
private Module LoadInternal(object module, TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
var loadedModule = new Module(module, typeInfo);
|
var loadedModule = new Module(this, module, typeInfo);
|
||||||
_modules[module] = loadedModule;
|
_modules[module] = loadedModule;
|
||||||
|
|
||||||
foreach (var cmd in loadedModule.Commands)
|
foreach (var cmd in loadedModule.Commands)
|
||||||
@@ -114,7 +223,7 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO: C#7 Candidate for tuple
|
//TODO: C#7 Candidate for tuple
|
||||||
public SearchResults Search(string input)
|
public SearchResult Search(string input)
|
||||||
{
|
{
|
||||||
string lowerInput = input.ToLowerInvariant();
|
string lowerInput = input.ToLowerInvariant();
|
||||||
|
|
||||||
@@ -125,21 +234,25 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
endPos = input.IndexOf(' ', startPos);
|
endPos = input.IndexOf(' ', startPos);
|
||||||
string cmdText = endPos == -1 ? input.Substring(startPos) : input.Substring(startPos, endPos - startPos);
|
string cmdText = endPos == -1 ? input.Substring(startPos) : input.Substring(startPos, endPos - startPos);
|
||||||
startPos = endPos + 1;
|
|
||||||
if (!_map.TryGetValue(cmdText, out group))
|
if (!_map.TryGetValue(cmdText, out group))
|
||||||
break;
|
break;
|
||||||
bestGroup = group;
|
bestGroup = group;
|
||||||
|
if (endPos == -1)
|
||||||
|
{
|
||||||
|
startPos = input.Length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
startPos = endPos + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImmutableArray<Command> cmds;
|
|
||||||
if (bestGroup != null)
|
if (bestGroup != null)
|
||||||
{
|
{
|
||||||
lock (bestGroup)
|
lock (bestGroup)
|
||||||
cmds = bestGroup.ToImmutableArray();
|
return SearchResult.FromSuccess(bestGroup.ToImmutableArray(), input.Substring(startPos));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
cmds = ImmutableArray.Create<Command>();
|
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
|
||||||
return new SearchResults(cmds, startPos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
public class Module
|
public class Module
|
||||||
{
|
{
|
||||||
|
public CommandService Service { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public IEnumerable<Command> Commands { get; }
|
public IEnumerable<Command> Commands { get; }
|
||||||
|
|
||||||
internal Module(object parent, TypeInfo typeInfo)
|
internal Module(CommandService service, object instance, TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
|
Service = service;
|
||||||
|
Name = typeInfo.Name;
|
||||||
|
|
||||||
List<Command> commands = new List<Command>();
|
List<Command> commands = new List<Command>();
|
||||||
SearchClass(parent, commands, typeInfo);
|
SearchClass(instance, commands, typeInfo);
|
||||||
Commands = commands;
|
Commands = commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchClass(object parent, List<Command> commands, TypeInfo typeInfo)
|
private void SearchClass(object instance, List<Command> commands, TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
foreach (var method in typeInfo.DeclaredMethods)
|
foreach (var method in typeInfo.DeclaredMethods)
|
||||||
{
|
{
|
||||||
var cmdAttr = method.GetCustomAttribute<CommandAttribute>();
|
var cmdAttr = method.GetCustomAttribute<CommandAttribute>();
|
||||||
if (cmdAttr != null)
|
if (cmdAttr != null)
|
||||||
commands.Add(new Command(cmdAttr, method));
|
commands.Add(new Command(this, instance, cmdAttr, method));
|
||||||
}
|
}
|
||||||
foreach (var type in typeInfo.DeclaredNestedTypes)
|
foreach (var type in typeInfo.DeclaredNestedTypes)
|
||||||
{
|
{
|
||||||
@@ -29,5 +35,8 @@ namespace Discord.Commands
|
|||||||
SearchClass(ReflectionUtils.CreateObject(type), commands, type);
|
SearchClass(ReflectionUtils.CreateObject(type), commands, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Name;
|
||||||
|
private string DebuggerDisplay => Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
src/Discord.Net.Commands/Readers/ChannelTypeReader.cs
Normal file
48
src/Discord.Net.Commands/Readers/ChannelTypeReader.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
internal class ChannelTypeReader<T> : TypeReader
|
||||||
|
where T : class, IChannel
|
||||||
|
{
|
||||||
|
public override async Task<TypeReaderResult> Read(IMessage context, string input)
|
||||||
|
{
|
||||||
|
IGuildChannel guildChannel = context.Channel as IGuildChannel;
|
||||||
|
IChannel result = null;
|
||||||
|
|
||||||
|
if (guildChannel != null)
|
||||||
|
{
|
||||||
|
//By Id
|
||||||
|
ulong id;
|
||||||
|
if (MentionUtils.TryParseChannel(input, out id) || ulong.TryParse(input, out id))
|
||||||
|
{
|
||||||
|
var channel = await guildChannel.Guild.GetChannelAsync(id).ConfigureAwait(false);
|
||||||
|
if (channel != null)
|
||||||
|
result = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
//By Name
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
var channels = await guildChannel.Guild.GetChannelsAsync().ConfigureAwait(false);
|
||||||
|
var filteredChannels = channels.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||||
|
if (filteredChannels.Length > 1)
|
||||||
|
return TypeReaderResult.FromError(CommandError.MultipleMatches, "Multiple channels found.");
|
||||||
|
else if (filteredChannels.Length == 1)
|
||||||
|
result = filteredChannels[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Channel not found.");
|
||||||
|
|
||||||
|
T castResult = result as T;
|
||||||
|
if (castResult == null)
|
||||||
|
return TypeReaderResult.FromError(CommandError.CastFailed, $"Channel is not a {typeof(T).Name}.");
|
||||||
|
else
|
||||||
|
return TypeReaderResult.FromSuccess(castResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/Discord.Net.Commands/Readers/GenericTypeReader.cs
Normal file
17
src/Discord.Net.Commands/Readers/GenericTypeReader.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
internal class GenericTypeReader : TypeReader
|
||||||
|
{
|
||||||
|
private readonly Func<IMessage, string, Task<TypeReaderResult>> _action;
|
||||||
|
|
||||||
|
public GenericTypeReader(Func<IMessage, string, Task<TypeReaderResult>> action)
|
||||||
|
{
|
||||||
|
_action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<TypeReaderResult> Read(IMessage context, string input) => _action(context, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Discord.Net.Commands/Readers/MessageTypeReader.cs
Normal file
24
src/Discord.Net.Commands/Readers/MessageTypeReader.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
internal class MessageTypeReader : TypeReader
|
||||||
|
{
|
||||||
|
public override Task<TypeReaderResult> Read(IMessage context, string input)
|
||||||
|
{
|
||||||
|
//By Id
|
||||||
|
ulong id;
|
||||||
|
if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id))
|
||||||
|
{
|
||||||
|
var msg = context.Channel.GetCachedMessage(id);
|
||||||
|
if (msg == null)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ObjectNotFound, "Message not found."));
|
||||||
|
else
|
||||||
|
return Task.FromResult(TypeReaderResult.FromSuccess(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse Message Id."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/Discord.Net.Commands/Readers/RoleTypeReader.cs
Normal file
36
src/Discord.Net.Commands/Readers/RoleTypeReader.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
internal class RoleTypeReader : TypeReader
|
||||||
|
{
|
||||||
|
public override Task<TypeReaderResult> Read(IMessage context, string input)
|
||||||
|
{
|
||||||
|
IGuildChannel guildChannel = context.Channel as IGuildChannel;
|
||||||
|
|
||||||
|
if (guildChannel != null)
|
||||||
|
{
|
||||||
|
//By Id
|
||||||
|
ulong id;
|
||||||
|
if (MentionUtils.TryParseRole(input, out id) || ulong.TryParse(input, out id))
|
||||||
|
{
|
||||||
|
var channel = guildChannel.Guild.GetRole(id);
|
||||||
|
if (channel != null)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromSuccess(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
//By Name
|
||||||
|
var roles = guildChannel.Guild.Roles;
|
||||||
|
var filteredRoles = roles.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||||
|
if (filteredRoles.Length > 1)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.MultipleMatches, "Multiple roles found."));
|
||||||
|
else if (filteredRoles.Length == 1)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromSuccess(filteredRoles[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ObjectNotFound, "Role not found."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/Discord.Net.Commands/Readers/TypeReader.cs
Normal file
9
src/Discord.Net.Commands/Readers/TypeReader.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
public abstract class TypeReader
|
||||||
|
{
|
||||||
|
public abstract Task<TypeReaderResult> Read(IMessage context, string input);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/Discord.Net.Commands/Readers/UserTypeReader.cs
Normal file
66
src/Discord.Net.Commands/Readers/UserTypeReader.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
internal class UserTypeReader<T> : TypeReader
|
||||||
|
where T : class, IUser
|
||||||
|
{
|
||||||
|
public override async Task<TypeReaderResult> Read(IMessage context, string input)
|
||||||
|
{
|
||||||
|
IGuildChannel guildChannel = context.Channel as IGuildChannel;
|
||||||
|
IUser result = null;
|
||||||
|
|
||||||
|
if (guildChannel != null)
|
||||||
|
{
|
||||||
|
//By Id
|
||||||
|
ulong id;
|
||||||
|
if (MentionUtils.TryParseUser(input, out id) || ulong.TryParse(input, out id))
|
||||||
|
{
|
||||||
|
var user = await guildChannel.Guild.GetUserAsync(id).ConfigureAwait(false);
|
||||||
|
if (user != null)
|
||||||
|
result = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
//By Username + Discriminator
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
int index = input.LastIndexOf('#');
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
string username = input.Substring(0, index);
|
||||||
|
ushort discriminator;
|
||||||
|
if (ushort.TryParse(input.Substring(index + 1), out discriminator))
|
||||||
|
{
|
||||||
|
var users = await guildChannel.Guild.GetUsersAsync().ConfigureAwait(false);
|
||||||
|
result = users.Where(x =>
|
||||||
|
x.DiscriminatorValue == discriminator &&
|
||||||
|
string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//By Username
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
var users = await guildChannel.Guild.GetUsersAsync().ConfigureAwait(false);
|
||||||
|
var filteredUsers = users.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||||
|
if (filteredUsers.Length > 1)
|
||||||
|
return TypeReaderResult.FromError(CommandError.MultipleMatches, "Multiple users found.");
|
||||||
|
else if (filteredUsers.Length == 1)
|
||||||
|
result = filteredUsers[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found.");
|
||||||
|
|
||||||
|
T castResult = result as T;
|
||||||
|
if (castResult == null)
|
||||||
|
return TypeReaderResult.FromError(CommandError.CastFailed, $"User is not a {typeof(T).Name}.");
|
||||||
|
else
|
||||||
|
return TypeReaderResult.FromSuccess(castResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/Discord.Net.Commands/Results/ExecuteResult.cs
Normal file
35
src/Discord.Net.Commands/Results/ExecuteResult.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
|
public struct ExecuteResult : IResult
|
||||||
|
{
|
||||||
|
public Exception Exception { get; }
|
||||||
|
|
||||||
|
public CommandError? Error { get; }
|
||||||
|
public string ErrorReason { get; }
|
||||||
|
|
||||||
|
public bool IsSuccess => !Error.HasValue;
|
||||||
|
|
||||||
|
private ExecuteResult(Exception exception, CommandError? error, string errorReason)
|
||||||
|
{
|
||||||
|
Exception = exception;
|
||||||
|
Error = error;
|
||||||
|
ErrorReason = errorReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ExecuteResult FromSuccess()
|
||||||
|
=> new ExecuteResult(null, null, null);
|
||||||
|
internal static ExecuteResult FromError(CommandError error, string reason)
|
||||||
|
=> new ExecuteResult(null, error, reason);
|
||||||
|
internal static ExecuteResult FromError(Exception ex)
|
||||||
|
=> new ExecuteResult(ex, CommandError.Exception, ex.Message);
|
||||||
|
internal static ExecuteResult FromError(ParseResult result)
|
||||||
|
=> new ExecuteResult(null, result.Error, result.ErrorReason);
|
||||||
|
|
||||||
|
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||||
|
private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/Discord.Net.Commands/Results/IResult.cs
Normal file
9
src/Discord.Net.Commands/Results/IResult.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
public interface IResult
|
||||||
|
{
|
||||||
|
CommandError? Error { get; }
|
||||||
|
string ErrorReason { get; }
|
||||||
|
bool IsSuccess { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/Discord.Net.Commands/Results/ParseResult.cs
Normal file
35
src/Discord.Net.Commands/Results/ParseResult.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
|
public struct ParseResult : IResult
|
||||||
|
{
|
||||||
|
public IReadOnlyList<object> Values { get; }
|
||||||
|
|
||||||
|
public CommandError? Error { get; }
|
||||||
|
public string ErrorReason { get; }
|
||||||
|
|
||||||
|
public bool IsSuccess => !Error.HasValue;
|
||||||
|
|
||||||
|
private ParseResult(IReadOnlyList<object> values, CommandError? error, string errorReason)
|
||||||
|
{
|
||||||
|
Values = values;
|
||||||
|
Error = error;
|
||||||
|
ErrorReason = errorReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ParseResult FromSuccess(IReadOnlyList<object> values)
|
||||||
|
=> new ParseResult(values, null, null);
|
||||||
|
internal static ParseResult FromError(CommandError error, string reason)
|
||||||
|
=> new ParseResult(null, error, reason);
|
||||||
|
internal static ParseResult FromError(SearchResult result)
|
||||||
|
=> new ParseResult(null, result.Error, result.ErrorReason);
|
||||||
|
internal static ParseResult FromError(TypeReaderResult result)
|
||||||
|
=> new ParseResult(null, result.Error, result.ErrorReason);
|
||||||
|
|
||||||
|
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||||
|
private string DebuggerDisplay => IsSuccess ? $"Success ({Values.Count} Values)" : $"{Error}: {ErrorReason}";
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/Discord.Net.Commands/Results/SearchResult.cs
Normal file
33
src/Discord.Net.Commands/Results/SearchResult.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
|
public struct SearchResult : IResult
|
||||||
|
{
|
||||||
|
public IReadOnlyList<Command> Commands { get; }
|
||||||
|
public string ArgText { get; }
|
||||||
|
|
||||||
|
public CommandError? Error { get; }
|
||||||
|
public string ErrorReason { get; }
|
||||||
|
|
||||||
|
public bool IsSuccess => !Error.HasValue;
|
||||||
|
|
||||||
|
private SearchResult(IReadOnlyList<Command> commands, string argText, CommandError? error, string errorReason)
|
||||||
|
{
|
||||||
|
Commands = commands;
|
||||||
|
ArgText = argText;
|
||||||
|
Error = error;
|
||||||
|
ErrorReason = errorReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SearchResult FromSuccess(IReadOnlyList<Command> commands, string argText)
|
||||||
|
=> new SearchResult(commands, argText, null, null);
|
||||||
|
internal static SearchResult FromError(CommandError error, string reason)
|
||||||
|
=> new SearchResult(null, null, error, reason);
|
||||||
|
|
||||||
|
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||||
|
private string DebuggerDisplay => IsSuccess ? $"Success ({Commands.Count} Results)" : $"{Error}: {ErrorReason}";
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/Discord.Net.Commands/Results/TypeReaderResult.cs
Normal file
30
src/Discord.Net.Commands/Results/TypeReaderResult.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
|
public struct TypeReaderResult : IResult
|
||||||
|
{
|
||||||
|
public object Value { get; }
|
||||||
|
|
||||||
|
public CommandError? Error { get; }
|
||||||
|
public string ErrorReason { get; }
|
||||||
|
|
||||||
|
public bool IsSuccess => !Error.HasValue;
|
||||||
|
|
||||||
|
private TypeReaderResult(object value, CommandError? error, string errorReason)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Error = error;
|
||||||
|
ErrorReason = errorReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TypeReaderResult FromSuccess(object value)
|
||||||
|
=> new TypeReaderResult(value, null, null);
|
||||||
|
public static TypeReaderResult FromError(CommandError error, string reason)
|
||||||
|
=> new TypeReaderResult(null, error, reason);
|
||||||
|
|
||||||
|
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||||
|
private string DebuggerDisplay => IsSuccess ? $"Success ({Value})" : $"{Error}: {ErrorReason}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Discord.Commands
|
|
||||||
{
|
|
||||||
public struct SearchResults
|
|
||||||
{
|
|
||||||
IReadOnlyList<Command> Commands { get; }
|
|
||||||
int ArgsPos { get; }
|
|
||||||
|
|
||||||
public SearchResults(IReadOnlyList<Command> commands, int argsPos)
|
|
||||||
{
|
|
||||||
Commands = commands;
|
|
||||||
ArgsPos = argsPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user