Added command groups, fixed several bugs

This commit is contained in:
RogueException
2016-06-27 06:56:24 -03:00
parent 6300a24eb5
commit 7bb890cbfe
9 changed files with 72 additions and 42 deletions

View File

@@ -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;
} }
} }
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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)
{ {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)
matches = ImmutableArray.CreateBuilder<Command>(group.Count);
for (int i = 0; i < group.Count; i++)
matches.Add(group[i]);
}
if (pos == -1)
{
pos = input.Length;
break; break;
} }
else
startPos = endPos + 1;
} }
if (bestGroup != null) if (matches != null)
{ return SearchResult.FromSuccess(input, matches.ToImmutable());
lock (bestGroup)
return SearchResult.FromSuccess(bestGroup.ToImmutableArray(), input.Substring(startPos));
}
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)

View File

@@ -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;

View File

@@ -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);
}
} }
} }

View File

@@ -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);