Added command groups, fixed several bugs
This commit is contained in:
@@ -5,10 +5,14 @@ namespace Discord.Commands
|
|||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class GroupAttribute : Attribute
|
public class GroupAttribute : Attribute
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Prefix { get; }
|
||||||
public GroupAttribute(string name)
|
public GroupAttribute()
|
||||||
{
|
{
|
||||||
Name = name;
|
Prefix = null;
|
||||||
|
}
|
||||||
|
public GroupAttribute(string prefix)
|
||||||
|
{
|
||||||
|
Prefix = prefix;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,14 @@ namespace Discord.Commands
|
|||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class ModuleAttribute : Attribute
|
public class ModuleAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
public string Prefix { get; }
|
||||||
|
public ModuleAttribute()
|
||||||
|
{
|
||||||
|
Prefix = null;
|
||||||
|
}
|
||||||
|
public ModuleAttribute(string prefix)
|
||||||
|
{
|
||||||
|
Prefix = prefix;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ namespace Discord.Commands
|
|||||||
public Module Module { get; }
|
public Module Module { get; }
|
||||||
public IReadOnlyList<CommandParameter> Parameters { get; }
|
public IReadOnlyList<CommandParameter> Parameters { get; }
|
||||||
|
|
||||||
internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo)
|
internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix)
|
||||||
{
|
{
|
||||||
Module = module;
|
Module = module;
|
||||||
_instance = instance;
|
_instance = instance;
|
||||||
|
|
||||||
Name = methodInfo.Name;
|
Name = methodInfo.Name;
|
||||||
Text = attribute.Text;
|
Text = groupPrefix + attribute.Text;
|
||||||
|
|
||||||
var description = methodInfo.GetCustomAttribute<DescriptionAttribute>();
|
var description = methodInfo.GetCustomAttribute<DescriptionAttribute>();
|
||||||
if (description != null)
|
if (description != null)
|
||||||
@@ -40,7 +40,7 @@ namespace Discord.Commands
|
|||||||
if (!searchResult.IsSuccess)
|
if (!searchResult.IsSuccess)
|
||||||
return ParseResult.FromError(searchResult);
|
return ParseResult.FromError(searchResult);
|
||||||
|
|
||||||
return await CommandParser.ParseArgs(this, msg, searchResult.ArgText, 0).ConfigureAwait(false);
|
return await CommandParser.ParseArgs(this, msg, searchResult.Text.Substring(Text.Length), 0).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
public async Task<ExecuteResult> Execute(IMessage msg, ParseResult parseResult)
|
public async Task<ExecuteResult> Execute(IMessage msg, ParseResult parseResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace Discord.Commands
|
|||||||
public CommandParameter(string name, string description, TypeReader reader, bool isOptional, bool isUnparsed, object defaultValue)
|
public CommandParameter(string name, string description, TypeReader reader, bool isOptional, bool isUnparsed, object defaultValue)
|
||||||
{
|
{
|
||||||
_reader = reader;
|
_reader = reader;
|
||||||
|
Name = name;
|
||||||
IsOptional = isOptional;
|
IsOptional = isOptional;
|
||||||
IsUnparsed = isUnparsed;
|
IsUnparsed = isUnparsed;
|
||||||
DefaultValue = defaultValue;
|
DefaultValue = defaultValue;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ namespace Discord.Commands
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null;
|
curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null;
|
||||||
if (curParam.IsUnparsed)
|
if (curParam != null && curParam.IsUnparsed)
|
||||||
{
|
{
|
||||||
argBuilder.Append(c);
|
argBuilder.Append(c);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -15,7 +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;
|
private readonly ConcurrentDictionary<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);
|
||||||
@@ -25,7 +25,7 @@ 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>
|
_typeReaders = new ConcurrentDictionary<Type, TypeReader>
|
||||||
{
|
{
|
||||||
[typeof(string)] = new GenericTypeReader((m, s) => Task.FromResult(TypeReaderResult.FromSuccess(s))),
|
[typeof(string)] = new GenericTypeReader((m, s) => Task.FromResult(TypeReaderResult.FromSuccess(s))),
|
||||||
[typeof(byte)] = new GenericTypeReader((m, s) =>
|
[typeof(byte)] = new GenericTypeReader((m, s) =>
|
||||||
@@ -143,19 +143,20 @@ namespace Discord.Commands
|
|||||||
throw new ArgumentException($"This module has already been loaded.");
|
throw new ArgumentException($"This module has already been loaded.");
|
||||||
|
|
||||||
var typeInfo = moduleInstance.GetType().GetTypeInfo();
|
var typeInfo = moduleInstance.GetType().GetTypeInfo();
|
||||||
if (typeInfo.GetCustomAttribute<ModuleAttribute>() == null)
|
var moduleAttr = typeInfo.GetCustomAttribute<ModuleAttribute>();
|
||||||
|
if (moduleAttr != null)
|
||||||
throw new ArgumentException($"Modules must be marked with ModuleAttribute.");
|
throw new ArgumentException($"Modules must be marked with ModuleAttribute.");
|
||||||
|
|
||||||
return LoadInternal(moduleInstance, typeInfo);
|
return LoadInternal(moduleInstance, moduleAttr, typeInfo);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_moduleLock.Release();
|
_moduleLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private Module LoadInternal(object moduleInstance, TypeInfo typeInfo)
|
private Module LoadInternal(object moduleInstance, ModuleAttribute moduleAttr, TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
var loadedModule = new Module(this, moduleInstance, typeInfo);
|
var loadedModule = new Module(this, moduleInstance, moduleAttr, typeInfo);
|
||||||
_modules[moduleInstance] = loadedModule;
|
_modules[moduleInstance] = loadedModule;
|
||||||
|
|
||||||
foreach (var cmd in loadedModule.Commands)
|
foreach (var cmd in loadedModule.Commands)
|
||||||
@@ -176,10 +177,11 @@ namespace Discord.Commands
|
|||||||
foreach (var type in assembly.ExportedTypes)
|
foreach (var type in assembly.ExportedTypes)
|
||||||
{
|
{
|
||||||
var typeInfo = type.GetTypeInfo();
|
var typeInfo = type.GetTypeInfo();
|
||||||
if (typeInfo.GetCustomAttribute<ModuleAttribute>() != null)
|
var moduleAttr = typeInfo.GetCustomAttribute<ModuleAttribute>();
|
||||||
|
if (moduleAttr != null)
|
||||||
{
|
{
|
||||||
var moduleInstance = ReflectionUtils.CreateObject(typeInfo);
|
var moduleInstance = ReflectionUtils.CreateObject(typeInfo);
|
||||||
modules.Add(LoadInternal(moduleInstance, typeInfo));
|
modules.Add(LoadInternal(moduleInstance, moduleAttr, typeInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return modules.ToImmutable();
|
return modules.ToImmutable();
|
||||||
@@ -239,30 +241,34 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
string lowerInput = input.ToLowerInvariant();
|
string lowerInput = input.ToLowerInvariant();
|
||||||
|
|
||||||
List<Command> bestGroup = null, group;
|
ImmutableArray<Command>.Builder matches = null;
|
||||||
int startPos = 0, endPos;
|
List<Command> group;
|
||||||
|
int pos = -1;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
endPos = input.IndexOf(' ', startPos);
|
pos = input.IndexOf(' ', pos + 1);
|
||||||
string cmdText = endPos == -1 ? input.Substring(startPos) : input.Substring(startPos, endPos - startPos);
|
string cmdText = pos == -1 ? input : input.Substring(0, pos);
|
||||||
if (!_map.TryGetValue(cmdText, out group))
|
if (!_map.TryGetValue(cmdText, out group))
|
||||||
break;
|
break;
|
||||||
bestGroup = group;
|
|
||||||
if (endPos == -1)
|
lock (group)
|
||||||
{
|
{
|
||||||
startPos = input.Length;
|
if (matches == null)
|
||||||
break;
|
matches = ImmutableArray.CreateBuilder<Command>(group.Count);
|
||||||
}
|
for (int i = 0; i < group.Count; i++)
|
||||||
else
|
matches.Add(group[i]);
|
||||||
startPos = endPos + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bestGroup != null)
|
if (pos == -1)
|
||||||
{
|
{
|
||||||
lock (bestGroup)
|
pos = input.Length;
|
||||||
return SearchResult.FromSuccess(bestGroup.ToImmutableArray(), input.Substring(startPos));
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches != null)
|
||||||
|
return SearchResult.FromSuccess(input, matches.ToImmutable());
|
||||||
else
|
else
|
||||||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
|
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
|
||||||
}
|
}
|
||||||
@@ -275,7 +281,7 @@ namespace Discord.Commands
|
|||||||
return searchResult;
|
return searchResult;
|
||||||
|
|
||||||
var commands = searchResult.Commands;
|
var commands = searchResult.Commands;
|
||||||
for (int i = 0; i < commands.Count; i++)
|
for (int i = commands.Count - 1; i >= 0; i++)
|
||||||
{
|
{
|
||||||
var parseResult = await commands[i].Parse(message, searchResult);
|
var parseResult = await commands[i].Parse(message, searchResult);
|
||||||
if (!parseResult.IsSuccess)
|
if (!parseResult.IsSuccess)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
public static bool HasStringPrefix(this IMessage msg, string str, ref int argPos)
|
public static bool HasStringPrefix(this IMessage msg, string str, ref int argPos)
|
||||||
{
|
{
|
||||||
var text = msg.RawText;
|
var text = msg.RawText;
|
||||||
str = str + ' ';
|
//str = str + ' ';
|
||||||
if (text.StartsWith(str))
|
if (text.StartsWith(str))
|
||||||
{
|
{
|
||||||
argPos = str.Length;
|
argPos = str.Length;
|
||||||
|
|||||||
@@ -12,29 +12,39 @@ namespace Discord.Commands
|
|||||||
public IEnumerable<Command> Commands { get; }
|
public IEnumerable<Command> Commands { get; }
|
||||||
internal object Instance { get; }
|
internal object Instance { get; }
|
||||||
|
|
||||||
internal Module(CommandService service, object instance, TypeInfo typeInfo)
|
internal Module(CommandService service, object instance, ModuleAttribute moduleAttr, TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
Service = service;
|
Service = service;
|
||||||
Name = typeInfo.Name;
|
Name = typeInfo.Name;
|
||||||
Instance = instance;
|
Instance = instance;
|
||||||
|
|
||||||
List<Command> commands = new List<Command>();
|
List<Command> commands = new List<Command>();
|
||||||
SearchClass(instance, commands, typeInfo);
|
SearchClass(instance, commands, typeInfo, moduleAttr.Prefix ?? "");
|
||||||
Commands = commands;
|
Commands = commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchClass(object instance, List<Command> commands, TypeInfo typeInfo)
|
private void SearchClass(object instance, List<Command> commands, TypeInfo typeInfo, string groupPrefix)
|
||||||
{
|
{
|
||||||
|
if (groupPrefix != "")
|
||||||
|
groupPrefix += " ";
|
||||||
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(this, instance, cmdAttr, method));
|
commands.Add(new Command(this, instance, cmdAttr, method, groupPrefix));
|
||||||
}
|
}
|
||||||
foreach (var type in typeInfo.DeclaredNestedTypes)
|
foreach (var type in typeInfo.DeclaredNestedTypes)
|
||||||
{
|
{
|
||||||
if (type.GetCustomAttribute<GroupAttribute>() != null)
|
var groupAttrib = type.GetCustomAttribute<GroupAttribute>();
|
||||||
SearchClass(ReflectionUtils.CreateObject(type), commands, type);
|
if (groupAttrib != null)
|
||||||
|
{
|
||||||
|
string nextGroupPrefix;
|
||||||
|
if (groupAttrib.Prefix != null)
|
||||||
|
nextGroupPrefix = groupPrefix + groupAttrib.Prefix ?? type.Name;
|
||||||
|
else
|
||||||
|
nextGroupPrefix = groupPrefix;
|
||||||
|
SearchClass(ReflectionUtils.CreateObject(type), commands, type, nextGroupPrefix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,24 +6,24 @@ namespace Discord.Commands
|
|||||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||||
public struct SearchResult : IResult
|
public struct SearchResult : IResult
|
||||||
{
|
{
|
||||||
|
public string Text { get; }
|
||||||
public IReadOnlyList<Command> Commands { get; }
|
public IReadOnlyList<Command> Commands { get; }
|
||||||
public string ArgText { 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 SearchResult(IReadOnlyList<Command> commands, string argText, CommandError? error, string errorReason)
|
private SearchResult(string text, IReadOnlyList<Command> commands, CommandError? error, string errorReason)
|
||||||
{
|
{
|
||||||
|
Text = text;
|
||||||
Commands = commands;
|
Commands = commands;
|
||||||
ArgText = argText;
|
|
||||||
Error = error;
|
Error = error;
|
||||||
ErrorReason = errorReason;
|
ErrorReason = errorReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static SearchResult FromSuccess(IReadOnlyList<Command> commands, string argText)
|
internal static SearchResult FromSuccess(string text, IReadOnlyList<Command> commands)
|
||||||
=> new SearchResult(commands, argText, null, null);
|
=> new SearchResult(text, commands, null, null);
|
||||||
internal static SearchResult FromError(CommandError error, string reason)
|
internal static SearchResult FromError(CommandError error, string reason)
|
||||||
=> new SearchResult(null, null, error, reason);
|
=> new SearchResult(null, null, error, reason);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user