diff --git a/src/Discord.Net.Commands/Attributes/GroupAttribute.cs b/src/Discord.Net.Commands/Attributes/GroupAttribute.cs index a39437862..3521f3f4f 100644 --- a/src/Discord.Net.Commands/Attributes/GroupAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/GroupAttribute.cs @@ -5,10 +5,14 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class)] public class GroupAttribute : Attribute { - public string Name { get; } - public GroupAttribute(string name) + public string Prefix { get; } + public GroupAttribute() { - Name = name; + Prefix = null; + } + public GroupAttribute(string prefix) + { + Prefix = prefix; } } } diff --git a/src/Discord.Net.Commands/Attributes/ModuleAttribute.cs b/src/Discord.Net.Commands/Attributes/ModuleAttribute.cs index 5e9481a45..59e6a6aca 100644 --- a/src/Discord.Net.Commands/Attributes/ModuleAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/ModuleAttribute.cs @@ -5,5 +5,14 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class)] public class ModuleAttribute : Attribute { + public string Prefix { get; } + public ModuleAttribute() + { + Prefix = null; + } + public ModuleAttribute(string prefix) + { + Prefix = prefix; + } } } diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index d3b94b94c..53c4758d5 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -19,13 +19,13 @@ namespace Discord.Commands public Module Module { get; } public IReadOnlyList 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; _instance = instance; Name = methodInfo.Name; - Text = attribute.Text; + Text = groupPrefix + attribute.Text; var description = methodInfo.GetCustomAttribute(); if (description != null) @@ -40,7 +40,7 @@ namespace Discord.Commands if (!searchResult.IsSuccess) 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 Execute(IMessage msg, ParseResult parseResult) { diff --git a/src/Discord.Net.Commands/CommandParameter.cs b/src/Discord.Net.Commands/CommandParameter.cs index 0a5952c48..e5b272ebe 100644 --- a/src/Discord.Net.Commands/CommandParameter.cs +++ b/src/Discord.Net.Commands/CommandParameter.cs @@ -18,6 +18,7 @@ namespace Discord.Commands public CommandParameter(string name, string description, TypeReader reader, bool isOptional, bool isUnparsed, object defaultValue) { _reader = reader; + Name = name; IsOptional = isOptional; IsUnparsed = isUnparsed; DefaultValue = defaultValue; diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index cd23cbbc3..262d3cb58 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -66,7 +66,7 @@ namespace Discord.Commands else { curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null; - if (curParam.IsUnparsed) + if (curParam != null && curParam.IsUnparsed) { argBuilder.Append(c); continue; diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index a4b4f8679..f3b623700 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -15,7 +15,7 @@ namespace Discord.Commands private readonly SemaphoreSlim _moduleLock; private readonly ConcurrentDictionary _modules; private readonly ConcurrentDictionary> _map; - private readonly Dictionary _typeReaders; + private readonly ConcurrentDictionary _typeReaders; public IEnumerable Modules => _modules.Select(x => x.Value); public IEnumerable Commands => _modules.SelectMany(x => x.Value.Commands); @@ -25,7 +25,7 @@ namespace Discord.Commands _moduleLock = new SemaphoreSlim(1, 1); _modules = new ConcurrentDictionary(); _map = new ConcurrentDictionary>(); - _typeReaders = new Dictionary + _typeReaders = new ConcurrentDictionary { [typeof(string)] = new GenericTypeReader((m, s) => Task.FromResult(TypeReaderResult.FromSuccess(s))), [typeof(byte)] = new GenericTypeReader((m, s) => @@ -143,19 +143,20 @@ namespace Discord.Commands throw new ArgumentException($"This module has already been loaded."); var typeInfo = moduleInstance.GetType().GetTypeInfo(); - if (typeInfo.GetCustomAttribute() == null) + var moduleAttr = typeInfo.GetCustomAttribute(); + if (moduleAttr != null) throw new ArgumentException($"Modules must be marked with ModuleAttribute."); - return LoadInternal(moduleInstance, typeInfo); + return LoadInternal(moduleInstance, moduleAttr, typeInfo); } finally { _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; foreach (var cmd in loadedModule.Commands) @@ -176,10 +177,11 @@ namespace Discord.Commands foreach (var type in assembly.ExportedTypes) { var typeInfo = type.GetTypeInfo(); - if (typeInfo.GetCustomAttribute() != null) + var moduleAttr = typeInfo.GetCustomAttribute(); + if (moduleAttr != null) { var moduleInstance = ReflectionUtils.CreateObject(typeInfo); - modules.Add(LoadInternal(moduleInstance, typeInfo)); + modules.Add(LoadInternal(moduleInstance, moduleAttr, typeInfo)); } } return modules.ToImmutable(); @@ -239,30 +241,34 @@ namespace Discord.Commands { string lowerInput = input.ToLowerInvariant(); - List bestGroup = null, group; - int startPos = 0, endPos; + ImmutableArray.Builder matches = null; + List group; + int pos = -1; while (true) { - endPos = input.IndexOf(' ', startPos); - string cmdText = endPos == -1 ? input.Substring(startPos) : input.Substring(startPos, endPos - startPos); + pos = input.IndexOf(' ', pos + 1); + string cmdText = pos == -1 ? input : input.Substring(0, pos); if (!_map.TryGetValue(cmdText, out group)) break; - bestGroup = group; - if (endPos == -1) + + lock (group) + { + if (matches == null) + matches = ImmutableArray.CreateBuilder(group.Count); + for (int i = 0; i < group.Count; i++) + matches.Add(group[i]); + } + + if (pos == -1) { - startPos = input.Length; + pos = input.Length; break; } - else - startPos = endPos + 1; } - if (bestGroup != null) - { - lock (bestGroup) - return SearchResult.FromSuccess(bestGroup.ToImmutableArray(), input.Substring(startPos)); - } + if (matches != null) + return SearchResult.FromSuccess(input, matches.ToImmutable()); else return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } @@ -275,7 +281,7 @@ namespace Discord.Commands return searchResult; 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); if (!parseResult.IsSuccess) diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index 2ba4973fe..b2e3e173c 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -15,7 +15,7 @@ public static bool HasStringPrefix(this IMessage msg, string str, ref int argPos) { var text = msg.RawText; - str = str + ' '; + //str = str + ' '; if (text.StartsWith(str)) { argPos = str.Length; diff --git a/src/Discord.Net.Commands/Module.cs b/src/Discord.Net.Commands/Module.cs index 0f7edb551..ea6e29c28 100644 --- a/src/Discord.Net.Commands/Module.cs +++ b/src/Discord.Net.Commands/Module.cs @@ -12,29 +12,39 @@ namespace Discord.Commands public IEnumerable Commands { 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; Name = typeInfo.Name; Instance = instance; List commands = new List(); - SearchClass(instance, commands, typeInfo); + SearchClass(instance, commands, typeInfo, moduleAttr.Prefix ?? ""); Commands = commands; } - private void SearchClass(object instance, List commands, TypeInfo typeInfo) + private void SearchClass(object instance, List commands, TypeInfo typeInfo, string groupPrefix) { + if (groupPrefix != "") + groupPrefix += " "; foreach (var method in typeInfo.DeclaredMethods) { var cmdAttr = method.GetCustomAttribute(); 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) { - if (type.GetCustomAttribute() != null) - SearchClass(ReflectionUtils.CreateObject(type), commands, type); + var groupAttrib = type.GetCustomAttribute(); + 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); + } } } diff --git a/src/Discord.Net.Commands/Results/SearchResult.cs b/src/Discord.Net.Commands/Results/SearchResult.cs index 0c7d671e3..3cda94ba4 100644 --- a/src/Discord.Net.Commands/Results/SearchResult.cs +++ b/src/Discord.Net.Commands/Results/SearchResult.cs @@ -6,24 +6,24 @@ namespace Discord.Commands [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct SearchResult : IResult { + public string Text { get; } public IReadOnlyList Commands { get; } - public string ArgText { get; } public CommandError? Error { get; } public string ErrorReason { get; } public bool IsSuccess => !Error.HasValue; - private SearchResult(IReadOnlyList commands, string argText, CommandError? error, string errorReason) + private SearchResult(string text, IReadOnlyList commands, CommandError? error, string errorReason) { + Text = text; Commands = commands; - ArgText = argText; Error = error; ErrorReason = errorReason; } - internal static SearchResult FromSuccess(IReadOnlyList commands, string argText) - => new SearchResult(commands, argText, null, null); + internal static SearchResult FromSuccess(string text, IReadOnlyList commands) + => new SearchResult(text, commands, null, null); internal static SearchResult FromError(CommandError error, string reason) => new SearchResult(null, null, error, reason);