Browse Source

Added command groups, fixed several bugs

tags/1.0-rc
RogueException 9 years ago
parent
commit
7bb890cbfe
9 changed files with 72 additions and 42 deletions
  1. +7
    -3
      src/Discord.Net.Commands/Attributes/GroupAttribute.cs
  2. +9
    -0
      src/Discord.Net.Commands/Attributes/ModuleAttribute.cs
  3. +3
    -3
      src/Discord.Net.Commands/Command.cs
  4. +1
    -0
      src/Discord.Net.Commands/CommandParameter.cs
  5. +1
    -1
      src/Discord.Net.Commands/CommandParser.cs
  6. +29
    -23
      src/Discord.Net.Commands/CommandService.cs
  7. +1
    -1
      src/Discord.Net.Commands/Extensions/MessageExtensions.cs
  8. +16
    -6
      src/Discord.Net.Commands/Module.cs
  9. +5
    -5
      src/Discord.Net.Commands/Results/SearchResult.cs

+ 7
- 3
src/Discord.Net.Commands/Attributes/GroupAttribute.cs View File

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

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

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

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

@@ -19,13 +19,13 @@ namespace Discord.Commands
public Module Module { 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;
_instance = instance;

Name = methodInfo.Name;
Text = attribute.Text;
Text = groupPrefix + attribute.Text;

var description = methodInfo.GetCustomAttribute<DescriptionAttribute>();
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<ExecuteResult> Execute(IMessage msg, ParseResult parseResult)
{


+ 1
- 0
src/Discord.Net.Commands/CommandParameter.cs View File

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


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

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


+ 29
- 23
src/Discord.Net.Commands/CommandService.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Commands
private readonly SemaphoreSlim _moduleLock;
private readonly ConcurrentDictionary<object, Module> _modules;
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<Command> Commands => _modules.SelectMany(x => x.Value.Commands);
@@ -25,7 +25,7 @@ namespace Discord.Commands
_moduleLock = new SemaphoreSlim(1, 1);
_modules = new ConcurrentDictionary<object, Module>();
_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(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<ModuleAttribute>() == null)
var moduleAttr = typeInfo.GetCustomAttribute<ModuleAttribute>();
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<ModuleAttribute>() != null)
var moduleAttr = typeInfo.GetCustomAttribute<ModuleAttribute>();
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<Command> bestGroup = null, group;
int startPos = 0, endPos;
ImmutableArray<Command>.Builder matches = null;
List<Command> 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<Command>(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)


+ 1
- 1
src/Discord.Net.Commands/Extensions/MessageExtensions.cs View File

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


+ 16
- 6
src/Discord.Net.Commands/Module.cs View File

@@ -12,29 +12,39 @@ namespace Discord.Commands
public IEnumerable<Command> 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<Command> commands = new List<Command>();
SearchClass(instance, commands, typeInfo);
SearchClass(instance, commands, typeInfo, moduleAttr.Prefix ?? "");
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)
{
var cmdAttr = method.GetCustomAttribute<CommandAttribute>();
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<GroupAttribute>() != null)
SearchClass(ReflectionUtils.CreateObject(type), commands, type);
var groupAttrib = type.GetCustomAttribute<GroupAttribute>();
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);
}
}
}



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

@@ -6,24 +6,24 @@ namespace Discord.Commands
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public struct SearchResult : IResult
{
public string Text { get; }
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)
private SearchResult(string text, IReadOnlyList<Command> commands, CommandError? error, string errorReason)
{
Text = text;
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 FromSuccess(string text, IReadOnlyList<Command> commands)
=> new SearchResult(text, commands, null, null);
internal static SearchResult FromError(CommandError error, string reason)
=> new SearchResult(null, null, error, reason);



Loading…
Cancel
Save