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;

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 Summary { get; set; }
@@ -36,7 +36,7 @@ namespace Discord.Commands.Builders
_aliases = new List<string>();
}
//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)
{
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);
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);
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();

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())
throw new InvalidOperationException("Could not find any valid modules from the given selection");*/
var topLevelGroups = validTypes.Where(x => x.DeclaringType == null);
var subGroups = validTypes.Intersect(topLevelGroups);

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

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

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

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

return result;
}

@@ -128,26 +148,32 @@ namespace Discord.Commands
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)
@@ -165,19 +191,19 @@ namespace Discord.Commands

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

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

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))
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))
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);
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)
{
@@ -161,8 +161,7 @@ namespace Discord.Commands
await _moduleLock.WaitAsync().ConfigureAwait(false);
try
{
ModuleInfo module;
if (!_typedModuleDefs.TryRemove(type, out module))
if (!_typedModuleDefs.TryRemove(type, out var module))
return false;

return RemoveModuleInternal(module);
@@ -201,15 +200,13 @@ namespace Discord.Commands
}
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 null;
}
internal TypeReader GetDefaultTypeReader(Type type)
{
TypeReader reader;
if (_defaultTypeReaders.TryGetValue(type, out reader))
if (_defaultTypeReaders.TryGetValue(type, out var reader))
return reader;
var typeInfo = type.GetTypeInfo();



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

@@ -4,8 +4,8 @@
{
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 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 string Name { get; }
@@ -181,7 +181,7 @@ namespace Discord.Commands
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
try
{
await _action(context, args, services).ConfigureAwait(false);
await _action(context, args, services, this).ConfigureAwait(false);
}
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);
}

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