Browse Source

C#7 features in commands, CommandInfo in ModuleBase

pull/689/head
FiniteReality 8 years ago
parent
commit
dadba5065a
7 changed files with 91 additions and 70 deletions
  1. +2
    -2
      src/Discord.Net.Commands/Builders/CommandBuilder.cs
  2. +1
    -1
      src/Discord.Net.Commands/Builders/ModuleBuilder.cs
  3. +73
    -47
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  4. +6
    -9
      src/Discord.Net.Commands/CommandService.cs
  5. +2
    -2
      src/Discord.Net.Commands/IModuleBase.cs
  6. +2
    -2
      src/Discord.Net.Commands/Info/CommandInfo.cs
  7. +5
    -7
      src/Discord.Net.Commands/ModuleBase.cs

+ 2
- 2
src/Discord.Net.Commands/Builders/CommandBuilder.cs View File

@@ -13,7 +13,7 @@ namespace Discord.Commands.Builders
private readonly List<string> _aliases; private readonly List<string> _aliases;


public ModuleBuilder Module { get; } public ModuleBuilder Module { get; }
internal Func<ICommandContext, object[], IServiceProvider, Task> Callback { get; set; }
internal Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> Callback { get; set; }


public string Name { get; set; } public string Name { get; set; }
public string Summary { get; set; } public string Summary { get; set; }
@@ -36,7 +36,7 @@ namespace Discord.Commands.Builders
_aliases = new List<string>(); _aliases = new List<string>();
} }
//User-defined //User-defined
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, Task> callback)
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> callback)
: this(module) : this(module)
{ {
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias));


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

@@ -74,7 +74,7 @@ namespace Discord.Commands.Builders
_preconditions.Add(precondition); _preconditions.Add(precondition);
return this; return this;
} }
public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IServiceProvider, Task> callback, Action<CommandBuilder> createFunc)
public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> callback, Action<CommandBuilder> createFunc)
{ {
var builder = new CommandBuilder(this, primaryAlias, callback); var builder = new CommandBuilder(this, primaryAlias, callback);
createFunc(builder); createFunc(builder);


+ 73
- 47
src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs View File

@@ -12,25 +12,42 @@ namespace Discord.Commands
{ {
private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo(); private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo();


public static IEnumerable<TypeInfo> Search(Assembly assembly)
public static async Task<IReadOnlyList<TypeInfo>> SearchAsync(Assembly assembly, CommandService service)
{ {
foreach (var type in assembly.ExportedTypes)
bool IsLoadableModule(TypeInfo info)
{ {
var typeInfo = type.GetTypeInfo();
if (IsValidModuleDefinition(typeInfo) &&
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute)))
return info.DeclaredMethods.Any(x => x.GetCustomAttribute<CommandAttribute>() != null) &&
info.GetCustomAttribute<DontAutoLoadAttribute>() == null;
}

List<TypeInfo> result = new List<TypeInfo>();

foreach (var typeInfo in assembly.DefinedTypes)
{
if (typeInfo.IsPublic)
{
if (IsValidModuleDefinition(typeInfo) &&
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute)))
{
result.Add(typeInfo);
}
}
else if (IsLoadableModule(typeInfo))
{ {
yield return typeInfo;
await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}.");
} }
} }

return result;
} }


public static Dictionary<Type, ModuleInfo> Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service);
public static Dictionary<Type, ModuleInfo> Build(IEnumerable<TypeInfo> validTypes, CommandService service)

public static Task<Dictionary<Type, ModuleInfo>> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service);
public static async Task<Dictionary<Type, ModuleInfo>> BuildAsync(IEnumerable<TypeInfo> validTypes, CommandService service)
{ {
/*if (!validTypes.Any()) /*if (!validTypes.Any())
throw new InvalidOperationException("Could not find any valid modules from the given selection");*/ throw new InvalidOperationException("Could not find any valid modules from the given selection");*/
var topLevelGroups = validTypes.Where(x => x.DeclaringType == null); var topLevelGroups = validTypes.Where(x => x.DeclaringType == null);
var subGroups = validTypes.Intersect(topLevelGroups); var subGroups = validTypes.Intersect(topLevelGroups);


@@ -48,10 +65,13 @@ namespace Discord.Commands


BuildModule(module, typeInfo, service); BuildModule(module, typeInfo, service);
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
builtTypes.Add(typeInfo);


result[typeInfo.AsType()] = module.Build(service); result[typeInfo.AsType()] = module.Build(service);
} }


await service._cmdLogger.DebugAsync($"Successfully built and loaded {builtTypes.Count} modules.").ConfigureAwait(false);

return result; return result;
} }


@@ -128,26 +148,32 @@ namespace Discord.Commands
foreach (var attribute in attributes) foreach (var attribute in attributes)
{ {
// TODO: C#7 type switch
if (attribute is CommandAttribute)
switch (attribute)
{ {
var cmdAttr = attribute as CommandAttribute;
builder.AddAliases(cmdAttr.Text);
builder.RunMode = cmdAttr.RunMode;
builder.Name = builder.Name ?? cmdAttr.Text;
case CommandAttribute command:
builder.AddAliases(command.Text);
builder.RunMode = command.RunMode;
builder.Name = builder.Name ?? command.Text;
break;
case NameAttribute name:
builder.Name = name.Text;
break;
case PriorityAttribute priority:
builder.Priority = priority.Priority;
break;
case SummaryAttribute summary:
builder.Summary = summary.Text;
break;
case RemarksAttribute remarks:
builder.Remarks = remarks.Text;
break;
case AliasAttribute alias:
builder.AddAliases(alias.Aliases);
break;
case PreconditionAttribute precondition:
builder.AddPrecondition(precondition);
break;
} }
else if (attribute is NameAttribute)
builder.Name = (attribute as NameAttribute).Text;
else if (attribute is PriorityAttribute)
builder.Priority = (attribute as PriorityAttribute).Priority;
else if (attribute is SummaryAttribute)
builder.Summary = (attribute as SummaryAttribute).Text;
else if (attribute is RemarksAttribute)
builder.Remarks = (attribute as RemarksAttribute).Text;
else if (attribute is AliasAttribute)
builder.AddAliases((attribute as AliasAttribute).Aliases);
else if (attribute is PreconditionAttribute)
builder.AddPrecondition(attribute as PreconditionAttribute);
} }


if (builder.Name == null) if (builder.Name == null)
@@ -165,19 +191,19 @@ namespace Discord.Commands


var createInstance = ReflectionUtils.CreateBuilder<IModuleBase>(typeInfo, service); var createInstance = ReflectionUtils.CreateBuilder<IModuleBase>(typeInfo, service);


builder.Callback = async (ctx, args, map) =>
builder.Callback = async (ctx, args, map, cmd) =>
{ {
var instance = createInstance(map); var instance = createInstance(map);
instance.SetContext(ctx); instance.SetContext(ctx);
try try
{ {
instance.BeforeExecute();
instance.BeforeExecute(cmd);
var task = method.Invoke(instance, args) as Task ?? Task.Delay(0); var task = method.Invoke(instance, args) as Task ?? Task.Delay(0);
await task.ConfigureAwait(false); await task.ConfigureAwait(false);
} }
finally finally
{ {
instance.AfterExecute();
instance.AfterExecute(cmd);
(instance as IDisposable)?.Dispose(); (instance as IDisposable)?.Dispose();
} }
}; };
@@ -195,24 +221,24 @@ namespace Discord.Commands


foreach (var attribute in attributes) foreach (var attribute in attributes)
{ {
// TODO: C#7 type switch
if (attribute is SummaryAttribute)
builder.Summary = (attribute as SummaryAttribute).Text;
else if (attribute is OverrideTypeReaderAttribute)
builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader);
else if (attribute is ParameterPreconditionAttribute)
builder.AddPrecondition(attribute as ParameterPreconditionAttribute);
else if (attribute is ParamArrayAttribute)
{
builder.IsMultiple = true;
paramType = paramType.GetElementType();
}
else if (attribute is RemainderAttribute)
switch (attribute)
{ {
if (position != count-1)
throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}");
builder.IsRemainder = true;
case SummaryAttribute summary:
builder.Summary = summary.Text;
break;
case OverrideTypeReaderAttribute typeReader:
builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader);
break;
case ParamArrayAttribute _:
builder.IsMultiple = true;
paramType = paramType.GetElementType();
break;
case RemainderAttribute _:
if (position != count - 1)
throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}");

builder.IsRemainder = true;
break;
} }
} }




+ 6
- 9
src/Discord.Net.Commands/CommandService.cs View File

@@ -95,7 +95,7 @@ namespace Discord.Commands
if (_typedModuleDefs.ContainsKey(type)) if (_typedModuleDefs.ContainsKey(type))
throw new ArgumentException($"This module has already been added."); throw new ArgumentException($"This module has already been added.");


var module = ModuleClassBuilder.Build(this, typeInfo).FirstOrDefault();
var module = (await ModuleClassBuilder.BuildAsync(this, typeInfo).ConfigureAwait(false)).FirstOrDefault();


if (module.Value == default(ModuleInfo)) if (module.Value == default(ModuleInfo))
throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?"); throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?");
@@ -114,8 +114,8 @@ namespace Discord.Commands
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
var types = ModuleClassBuilder.Search(assembly).ToArray();
var moduleDefs = ModuleClassBuilder.Build(types, this);
var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false);
var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this).ConfigureAwait(false);


foreach (var info in moduleDefs) foreach (var info in moduleDefs)
{ {
@@ -161,8 +161,7 @@ namespace Discord.Commands
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
{ {
ModuleInfo module;
if (!_typedModuleDefs.TryRemove(type, out module))
if (!_typedModuleDefs.TryRemove(type, out var module))
return false; return false;


return RemoveModuleInternal(module); return RemoveModuleInternal(module);
@@ -201,15 +200,13 @@ namespace Discord.Commands
} }
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type) internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
{ {
ConcurrentDictionary<Type, TypeReader> definedTypeReaders;
if (_typeReaders.TryGetValue(type, out definedTypeReaders))
if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
return definedTypeReaders; return definedTypeReaders;
return null; return null;
} }
internal TypeReader GetDefaultTypeReader(Type type) internal TypeReader GetDefaultTypeReader(Type type)
{ {
TypeReader reader;
if (_defaultTypeReaders.TryGetValue(type, out reader))
if (_defaultTypeReaders.TryGetValue(type, out var reader))
return reader; return reader;
var typeInfo = type.GetTypeInfo(); var typeInfo = type.GetTypeInfo();




+ 2
- 2
src/Discord.Net.Commands/IModuleBase.cs View File

@@ -4,8 +4,8 @@
{ {
void SetContext(ICommandContext context); void SetContext(ICommandContext context);


void BeforeExecute();
void BeforeExecute(CommandInfo command);
void AfterExecute();
void AfterExecute(CommandInfo command);
} }
} }

+ 2
- 2
src/Discord.Net.Commands/Info/CommandInfo.cs View File

@@ -18,7 +18,7 @@ namespace Discord.Commands
private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();


private readonly Func<ICommandContext, object[], IServiceProvider, Task> _action;
private readonly Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> _action;


public ModuleInfo Module { get; } public ModuleInfo Module { get; }
public string Name { get; } public string Name { get; }
@@ -181,7 +181,7 @@ namespace Discord.Commands
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
try try
{ {
await _action(context, args, services).ConfigureAwait(false);
await _action(context, args, services, this).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {


+ 5
- 7
src/Discord.Net.Commands/ModuleBase.cs View File

@@ -15,11 +15,11 @@ namespace Discord.Commands
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false);
} }


protected virtual void BeforeExecute()
protected virtual void BeforeExecute(CommandInfo command)
{ {
} }


protected virtual void AfterExecute()
protected virtual void AfterExecute(CommandInfo command)
{ {
} }


@@ -27,13 +27,11 @@ namespace Discord.Commands
void IModuleBase.SetContext(ICommandContext context) void IModuleBase.SetContext(ICommandContext context)
{ {
var newValue = context as T; var newValue = context as T;
if (newValue == null)
throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}");
Context = newValue;
Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}");
} }


void IModuleBase.BeforeExecute() => BeforeExecute();
void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command);


void IModuleBase.AfterExecute() => AfterExecute();
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command);
} }
} }

Loading…
Cancel
Save