From dadba5065a5d484674ce76f6315683b5e7ab0a6b Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sun, 4 Jun 2017 23:07:51 +0100 Subject: [PATCH] C#7 features in commands, CommandInfo in ModuleBase --- .../Builders/CommandBuilder.cs | 4 +- .../Builders/ModuleBuilder.cs | 2 +- .../Builders/ModuleClassBuilder.cs | 120 +++++++++++------- src/Discord.Net.Commands/CommandService.cs | 15 +-- src/Discord.Net.Commands/IModuleBase.cs | 4 +- src/Discord.Net.Commands/Info/CommandInfo.cs | 4 +- src/Discord.Net.Commands/ModuleBase.cs | 12 +- 7 files changed, 91 insertions(+), 70 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 8c2207f10..30db1fa62 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -13,7 +13,7 @@ namespace Discord.Commands.Builders private readonly List _aliases; public ModuleBuilder Module { get; } - internal Func Callback { get; set; } + internal Func Callback { get; set; } public string Name { get; set; } public string Summary { get; set; } @@ -36,7 +36,7 @@ namespace Discord.Commands.Builders _aliases = new List(); } //User-defined - internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) + internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) : this(module) { Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index d79239057..525907b8b 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -74,7 +74,7 @@ namespace Discord.Commands.Builders _preconditions.Add(precondition); return this; } - public ModuleBuilder AddCommand(string primaryAlias, Func callback, Action createFunc) + public ModuleBuilder AddCommand(string primaryAlias, Func callback, Action createFunc) { var builder = new CommandBuilder(this, primaryAlias, callback); createFunc(builder); diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index fe35e3b2a..401396900 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -12,25 +12,42 @@ namespace Discord.Commands { private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo(); - public static IEnumerable Search(Assembly assembly) + public static async Task> 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() != null) && + info.GetCustomAttribute() == null; + } + + List result = new List(); + + 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 Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service); - public static Dictionary Build(IEnumerable validTypes, CommandService service) + + public static Task> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service); + public static async Task> BuildAsync(IEnumerable 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(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; } } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index f526e8f3b..41e6afe33 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -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 GetTypeReaders(Type type) { - ConcurrentDictionary 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(); diff --git a/src/Discord.Net.Commands/IModuleBase.cs b/src/Discord.Net.Commands/IModuleBase.cs index fda768b53..479724ae3 100644 --- a/src/Discord.Net.Commands/IModuleBase.cs +++ b/src/Discord.Net.Commands/IModuleBase.cs @@ -4,8 +4,8 @@ { void SetContext(ICommandContext context); - void BeforeExecute(); + void BeforeExecute(CommandInfo command); - void AfterExecute(); + void AfterExecute(CommandInfo command); } } diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index f187460d5..afaed2368 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -18,7 +18,7 @@ namespace Discord.Commands private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); - private readonly Func _action; + private readonly Func _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) { diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index ed0b49006..f51656e40 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -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); } }