From b7a5ee6542477d3e3b1a73443ba55b1f033e7aea Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 19 Nov 2016 15:12:04 +0000 Subject: [PATCH 1/6] Parameter preconditions and typereader overriding --- .../Attributes/OverrideTypeReaderAttribute.cs | 22 ++++++++++ .../ParameterPreconditionAttribute.cs | 11 +++++ .../Builders/ModuleClassBuilder.cs | 4 ++ .../Builders/ParameterBuilder.cs | 18 +++++++- src/Discord.Net.Commands/Info/CommandInfo.cs | 19 ++++++++- .../Info/ParameterInfo.cs | 42 +++++++++++++++---- 6 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs create mode 100644 src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs new file mode 100644 index 000000000..e14be063f --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -0,0 +1,22 @@ +using System; + +using System.Reflection; + +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Parameter)] + public class OverrideTypeReaderAttribute : Attribute + { + private readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); + + public Type TypeReader { get; } + + public OverrideTypeReaderAttribute(Type overridenType) + { + if (!_typeReaderTypeInfo.IsAssignableFrom(overridenType.GetTypeInfo())) + throw new ArgumentException($"{nameof(overridenType)} must inherit from {nameof(TypeReader)}"); + + TypeReader = overridenType; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs new file mode 100644 index 000000000..f2ef78c05 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] + public abstract class ParameterPreconditionAttribute : Attribute + { + public abstract Task CheckPermissions(CommandContext context, ParameterInfo parameter, object value); + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index aaec43161..1775cc1fe 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -182,6 +182,10 @@ namespace Discord.Commands // TODO: C#7 type switch if (attribute is SummaryAttribute) builder.Summary = (attribute as SummaryAttribute).Text; + else if (attribute is OverrideTypeReaderAttribute) + builder.TypeReader = service.GetTypeReader((attribute as OverrideTypeReaderAttribute).TypeReader); + else if (attribute is ParameterPreconditionAttribute) + builder.AddPrecondition(attribute as ParameterPreconditionAttribute); else if (attribute is ParamArrayAttribute) { builder.IsMultiple = true; diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 801a10080..6b941a1c7 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -1,10 +1,14 @@ using System; using System.Reflection; +using System.Collections.Generic; + namespace Discord.Commands.Builders { public class ParameterBuilder { + private readonly List _preconditions; + public CommandBuilder Command { get; } public string Name { get; internal set; } public Type ParameterType { get; internal set; } @@ -16,16 +20,20 @@ namespace Discord.Commands.Builders public object DefaultValue { get; set; } public string Summary { get; set; } + public IReadOnlyList Preconditions => _preconditions; + //Automatic internal ParameterBuilder(CommandBuilder command) { + _preconditions = new List(); + Command = command; } //User-defined internal ParameterBuilder(CommandBuilder command, string name, Type type) : this(command) { - Preconditions.NotNull(name, nameof(name)); + Discord.Preconditions.NotNull(name, nameof(name)); Name = name; SetType(type); @@ -49,7 +57,7 @@ namespace Discord.Commands.Builders } public ParameterBuilder WithDefault(object defaultValue) { - DefaultValue = defaultValue; + DefaultValue = defaultValue; return this; } public ParameterBuilder WithIsOptional(bool isOptional) @@ -68,6 +76,12 @@ namespace Discord.Commands.Builders return this; } + public ParameterBuilder AddPrecondition(ParameterPreconditionAttribute precondition) + { + _preconditions.Add(precondition); + return this; + } + internal ParameterInfo Build(CommandInfo info) { if (TypeReader == null) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 571c47e13..a91a1f9f4 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -135,9 +135,26 @@ namespace Discord.Commands if (map == null) map = DependencyMap.Empty; + object[] args = null; + + try + { + args = GenerateArgs(argList, paramList); + } + catch (Exception ex) + { + return ExecuteResult.FromError(ex); + } + + foreach (var parameter in Parameters) + { + var result = await parameter.CheckPreconditionsAsync(context, args, map).ConfigureAwait(false); + if (!result.IsSuccess) + return ExecuteResult.FromError(result); + } + try { - var args = GenerateArgs(argList, paramList); switch (RunMode) { case RunMode.Sync: //Always sync diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 18c5e653c..2ef4b89c4 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using Discord.Commands.Builders; @@ -10,6 +12,17 @@ namespace Discord.Commands { private readonly TypeReader _reader; + public CommandInfo Command { get; } + public string Name { get; } + public string Summary { get; } + public bool IsOptional { get; } + public bool IsRemainder { get; } + public bool IsMultiple { get; } + public Type Type { get; } + public object DefaultValue { get; } + + public IReadOnlyList Preconditions { get; } + internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) { Command = command; @@ -23,17 +36,30 @@ namespace Discord.Commands Type = builder.ParameterType; DefaultValue = builder.DefaultValue; + Preconditions = builder.Preconditions.ToImmutableArray(); + _reader = builder.TypeReader; } - public CommandInfo Command { get; } - public string Name { get; } - public string Summary { get; } - public bool IsOptional { get; } - public bool IsRemainder { get; } - public bool IsMultiple { get; } - public Type Type { get; } - public object DefaultValue { get; } + public async Task CheckPreconditionsAsync(CommandContext context, object[] args, IDependencyMap map = null) + { + if (map == null) + map = DependencyMap.Empty; + + int position = 0; + for(position = 0; position < Command.Parameters.Count; position++) + if (Command.Parameters[position] == this) + break; + + foreach (var precondition in Preconditions) + { + var result = await precondition.CheckPermissions(context, this, args[position]).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + return PreconditionResult.FromSuccess(); + } public async Task Parse(CommandContext context, string input) { From f11f416024007f8f5dfa665167c142807506dee3 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 19 Nov 2016 21:09:49 +0000 Subject: [PATCH 2/6] Add IDependencyMap to parameter preconditions --- .../Attributes/ParameterPreconditionAttribute.cs | 2 +- src/Discord.Net.Commands/Info/ParameterInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs index f2ef78c05..3bf8d177a 100644 --- a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs @@ -6,6 +6,6 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] public abstract class ParameterPreconditionAttribute : Attribute { - public abstract Task CheckPermissions(CommandContext context, ParameterInfo parameter, object value); + public abstract Task CheckPermissions(CommandContext context, ParameterInfo parameter, object value, IDependencyMap map); } } \ No newline at end of file diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 2ef4b89c4..f8a97647a 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -53,7 +53,7 @@ namespace Discord.Commands foreach (var precondition in Preconditions) { - var result = await precondition.CheckPermissions(context, this, args[position]).ConfigureAwait(false); + var result = await precondition.CheckPermissions(context, this, args[position], map).ConfigureAwait(false); if (!result.IsSuccess) return result; } From 156483bf717ca9d11d9331c08e4f32511b25a2a6 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 19 Nov 2016 21:11:30 +0000 Subject: [PATCH 3/6] Rename `overridenType` to `overridenTypeReader` The previous name was causing some confusion --- .../Attributes/OverrideTypeReaderAttribute.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index e14be063f..8134b06b8 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -11,12 +11,12 @@ namespace Discord.Commands public Type TypeReader { get; } - public OverrideTypeReaderAttribute(Type overridenType) + public OverrideTypeReaderAttribute(Type overridenTypeReader) { - if (!_typeReaderTypeInfo.IsAssignableFrom(overridenType.GetTypeInfo())) - throw new ArgumentException($"{nameof(overridenType)} must inherit from {nameof(TypeReader)}"); + if (!_typeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) + throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}"); - TypeReader = overridenType; + TypeReader = overridenTypeReader; } } } \ No newline at end of file From d2d7b4dce7bb578eefbf078428f1ae0b34f33437 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 19 Nov 2016 21:16:28 +0000 Subject: [PATCH 4/6] Make `_typeReaderTypeInfo` static Seems I missed this originally, whoops. --- .../Attributes/OverrideTypeReaderAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index 8134b06b8..37f685c95 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -7,7 +7,7 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Parameter)] public class OverrideTypeReaderAttribute : Attribute { - private readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); + private static readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); public Type TypeReader { get; } From 254e874c999336179dd878dd733cbcb53820cc03 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Mon, 21 Nov 2016 18:46:21 +0000 Subject: [PATCH 5/6] Fix OverrideTypeReader This commit also adds a TypeReaders property to CommandService, so it is possible to see all of the registered TypeReaders. This makes it possible for users to implement their own parsing instead of using the built-in parsing. --- .../Builders/ModuleClassBuilder.cs | 48 +++++++--- .../Builders/ParameterBuilder.cs | 7 +- src/Discord.Net.Commands/CommandService.cs | 87 ++++++++++--------- 3 files changed, 88 insertions(+), 54 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 1775cc1fe..aaa96fb8e 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -183,7 +183,9 @@ namespace Discord.Commands if (attribute is SummaryAttribute) builder.Summary = (attribute as SummaryAttribute).Text; else if (attribute is OverrideTypeReaderAttribute) - builder.TypeReader = service.GetTypeReader((attribute as OverrideTypeReaderAttribute).TypeReader); + { + builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader); + } else if (attribute is ParameterPreconditionAttribute) builder.AddPrecondition(attribute as ParameterPreconditionAttribute); else if (attribute is ParamArrayAttribute) @@ -200,23 +202,47 @@ namespace Discord.Commands } } - var reader = service.GetTypeReader(paramType); - if (reader == null) + if (builder.TypeReader == null) { - var paramTypeInfo = paramType.GetTypeInfo(); - if (paramTypeInfo.IsEnum) + var readers = service.GetTypeReaders(paramType); + var reader = readers?.FirstOrDefault(); + + if (reader == null) { - reader = EnumTypeReader.GetReader(paramType); - service.AddTypeReader(paramType, reader); + var paramTypeInfo = paramType.GetTypeInfo(); + if (paramTypeInfo.IsEnum) + { + reader = EnumTypeReader.GetReader(paramType); + service.AddTypeReader(paramType, reader); + } + else + { + throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?"); + } } - else + + builder.ParameterType = paramType; + builder.TypeReader = reader; + } + } + + private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType) + { + var readers = service.GetTypeReaders(paramType); + if (readers != null) + { + var reader = readers.FirstOrDefault(x => x.GetType() == typeReaderType); + if (reader != default(TypeReader)) { - throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?"); + return reader; } } - builder.ParameterType = paramType; - builder.TypeReader = reader; + //could not find any registered type reader: try to create one + var typeReader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty); + service.AddTypeReader(paramType, typeReader); + + return typeReader; } private static bool IsValidModuleDefinition(TypeInfo typeInfo) diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 6b941a1c7..89f89b3cf 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reflection; using System.Collections.Generic; @@ -41,7 +42,11 @@ namespace Discord.Commands.Builders internal void SetType(Type type) { - TypeReader = Command.Module.Service.GetTypeReader(type); + var readers = Command.Module.Service.GetTypeReaders(type); + if (readers == null) + throw new InvalidOperationException($"{type} does not have a TypeReader registered for it"); + + TypeReader = readers.FirstOrDefault(); if (type.GetTypeInfo().IsValueType) DefaultValue = Activator.CreateInstance(type); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index b6659fea3..285a35432 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -14,8 +14,8 @@ namespace Discord.Commands public class CommandService { private readonly SemaphoreSlim _moduleLock; - private readonly ConcurrentDictionary _typedModuleDefs; - private readonly ConcurrentDictionary _typeReaders; + private readonly ConcurrentDictionary _typedModuleDefs; + private readonly ConcurrentDictionary> _typeReaders; private readonly ConcurrentBag _moduleDefs; private readonly CommandMap _map; @@ -24,6 +24,7 @@ namespace Discord.Commands public IEnumerable Modules => _moduleDefs.Select(x => x); public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); + public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value, (a, value) => new {a.Key, value}).ToLookup(x => x.Key, x => x.value); public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) @@ -32,41 +33,41 @@ namespace Discord.Commands _typedModuleDefs = new ConcurrentDictionary(); _moduleDefs = new ConcurrentBag(); _map = new CommandMap(); - _typeReaders = new ConcurrentDictionary + _typeReaders = new ConcurrentDictionary> { - [typeof(bool)] = new SimpleTypeReader(), - [typeof(char)] = new SimpleTypeReader(), - [typeof(string)] = new SimpleTypeReader(), - [typeof(byte)] = new SimpleTypeReader(), - [typeof(sbyte)] = new SimpleTypeReader(), - [typeof(ushort)] = new SimpleTypeReader(), - [typeof(short)] = new SimpleTypeReader(), - [typeof(uint)] = new SimpleTypeReader(), - [typeof(int)] = new SimpleTypeReader(), - [typeof(ulong)] = new SimpleTypeReader(), - [typeof(long)] = new SimpleTypeReader(), - [typeof(float)] = new SimpleTypeReader(), - [typeof(double)] = new SimpleTypeReader(), - [typeof(decimal)] = new SimpleTypeReader(), - [typeof(DateTime)] = new SimpleTypeReader(), - [typeof(DateTimeOffset)] = new SimpleTypeReader(), - [typeof(TimeSpan)] = new SimpleTypeReader(), - [typeof(IMessage)] = new MessageTypeReader(), - [typeof(IUserMessage)] = new MessageTypeReader(), - [typeof(IChannel)] = new ChannelTypeReader(), - [typeof(IDMChannel)] = new ChannelTypeReader(), - [typeof(IGroupChannel)] = new ChannelTypeReader(), - [typeof(IGuildChannel)] = new ChannelTypeReader(), - [typeof(IMessageChannel)] = new ChannelTypeReader(), - [typeof(IPrivateChannel)] = new ChannelTypeReader(), - [typeof(ITextChannel)] = new ChannelTypeReader(), - [typeof(IVoiceChannel)] = new ChannelTypeReader(), - - [typeof(IRole)] = new RoleTypeReader(), - - [typeof(IUser)] = new UserTypeReader(), - [typeof(IGroupUser)] = new UserTypeReader(), - [typeof(IGuildUser)] = new UserTypeReader(), + [typeof(bool)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(char)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(string)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(byte)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(sbyte)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(ushort)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(short)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(uint)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(int)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(ulong)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(long)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(float)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(double)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(decimal)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(DateTime)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(DateTimeOffset)] = new ConcurrentBag{new SimpleTypeReader()}, + + [typeof(IMessage)] = new ConcurrentBag{new MessageTypeReader()}, + [typeof(IUserMessage)] = new ConcurrentBag{new MessageTypeReader()}, + [typeof(IChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IDMChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IGroupChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IGuildChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IMessageChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IPrivateChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(ITextChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + [typeof(IVoiceChannel)] = new ConcurrentBag{new ChannelTypeReader()}, + + [typeof(IRole)] = new ConcurrentBag{new RoleTypeReader()}, + + [typeof(IUser)] = new ConcurrentBag{new UserTypeReader()}, + [typeof(IGroupUser)] = new ConcurrentBag{new UserTypeReader()}, + [typeof(IGuildUser)] = new ConcurrentBag{new UserTypeReader()}, }; _caseSensitive = config.CaseSensitiveCommands; _defaultRunMode = config.DefaultRunMode; @@ -196,17 +197,19 @@ namespace Discord.Commands //Type Readers public void AddTypeReader(TypeReader reader) { - _typeReaders[typeof(T)] = reader; + var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentBag()); + readers.Add(reader); } public void AddTypeReader(Type type, TypeReader reader) { - _typeReaders[type] = reader; + var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentBag()); + readers.Add(reader); } - internal TypeReader GetTypeReader(Type type) + internal IEnumerable GetTypeReaders(Type type) { - TypeReader reader; - if (_typeReaders.TryGetValue(type, out reader)) - return reader; + ConcurrentBag definedTypeReaders; + if (_typeReaders.TryGetValue(type, out definedTypeReaders)) + return definedTypeReaders; return null; } From 704b2b75f462641f0d69076acb9f0a884349ba13 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Fri, 25 Nov 2016 20:22:06 +0000 Subject: [PATCH 6/6] Fix changes after review --- .../Builders/ModuleClassBuilder.cs | 19 ++-- .../Builders/ParameterBuilder.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 94 ++++++++++--------- src/Discord.Net.Commands/Info/CommandInfo.cs | 23 ++--- 4 files changed, 66 insertions(+), 72 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index aaa96fb8e..e8dc60de8 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -183,9 +183,7 @@ namespace Discord.Commands 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) @@ -204,8 +202,7 @@ namespace Discord.Commands if (builder.TypeReader == null) { - var readers = service.GetTypeReaders(paramType); - var reader = readers?.FirstOrDefault(); + var reader = service.GetDefaultTypeReader(paramType); if (reader == null) { @@ -229,20 +226,16 @@ namespace Discord.Commands private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType) { var readers = service.GetTypeReaders(paramType); + TypeReader reader = null; if (readers != null) - { - var reader = readers.FirstOrDefault(x => x.GetType() == typeReaderType); - if (reader != default(TypeReader)) - { + if (readers.TryGetValue(typeReaderType, out reader)) return reader; - } - } //could not find any registered type reader: try to create one - var typeReader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty); - service.AddTypeReader(paramType, typeReader); + reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty); + service.AddTypeReader(paramType, reader); - return typeReader; + return reader; } private static bool IsValidModuleDefinition(TypeInfo typeInfo) diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 89f89b3cf..d4cf598ec 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -46,7 +46,7 @@ namespace Discord.Commands.Builders if (readers == null) throw new InvalidOperationException($"{type} does not have a TypeReader registered for it"); - TypeReader = readers.FirstOrDefault(); + TypeReader = readers.FirstOrDefault().Value; if (type.GetTypeInfo().IsValueType) DefaultValue = Activator.CreateInstance(type); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 285a35432..3dc1ca5d6 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -14,8 +14,9 @@ namespace Discord.Commands public class CommandService { private readonly SemaphoreSlim _moduleLock; - private readonly ConcurrentDictionary _typedModuleDefs; - private readonly ConcurrentDictionary> _typeReaders; + private readonly ConcurrentDictionary _typedModuleDefs; + private readonly ConcurrentDictionary> _typeReaders; + private readonly ConcurrentDictionary _defaultTypeReaders; private readonly ConcurrentBag _moduleDefs; private readonly CommandMap _map; @@ -24,7 +25,7 @@ namespace Discord.Commands public IEnumerable Modules => _moduleDefs.Select(x => x); public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); - public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value, (a, value) => new {a.Key, value}).ToLookup(x => x.Key, x => x.value); + public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new {y.Key, y.Value})).ToLookup(x => x.Key, x => x.Value); public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) @@ -33,41 +34,43 @@ namespace Discord.Commands _typedModuleDefs = new ConcurrentDictionary(); _moduleDefs = new ConcurrentBag(); _map = new CommandMap(); - _typeReaders = new ConcurrentDictionary> + _typeReaders = new ConcurrentDictionary>(); + + _defaultTypeReaders = new ConcurrentDictionary { - [typeof(bool)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(char)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(string)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(byte)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(sbyte)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(ushort)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(short)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(uint)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(int)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(ulong)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(long)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(float)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(double)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(decimal)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(DateTime)] = new ConcurrentBag{new SimpleTypeReader()}, - [typeof(DateTimeOffset)] = new ConcurrentBag{new SimpleTypeReader()}, + [typeof(bool)] = new SimpleTypeReader(), + [typeof(char)] = new SimpleTypeReader(), + [typeof(string)] = new SimpleTypeReader(), + [typeof(byte)] = new SimpleTypeReader(), + [typeof(sbyte)] = new SimpleTypeReader(), + [typeof(ushort)] = new SimpleTypeReader(), + [typeof(short)] = new SimpleTypeReader(), + [typeof(uint)] = new SimpleTypeReader(), + [typeof(int)] = new SimpleTypeReader(), + [typeof(ulong)] = new SimpleTypeReader(), + [typeof(long)] = new SimpleTypeReader(), + [typeof(float)] = new SimpleTypeReader(), + [typeof(double)] = new SimpleTypeReader(), + [typeof(decimal)] = new SimpleTypeReader(), + [typeof(DateTime)] = new SimpleTypeReader(), + [typeof(DateTimeOffset)] = new SimpleTypeReader(), - [typeof(IMessage)] = new ConcurrentBag{new MessageTypeReader()}, - [typeof(IUserMessage)] = new ConcurrentBag{new MessageTypeReader()}, - [typeof(IChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IDMChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IGroupChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IGuildChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IMessageChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IPrivateChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(ITextChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - [typeof(IVoiceChannel)] = new ConcurrentBag{new ChannelTypeReader()}, - - [typeof(IRole)] = new ConcurrentBag{new RoleTypeReader()}, - - [typeof(IUser)] = new ConcurrentBag{new UserTypeReader()}, - [typeof(IGroupUser)] = new ConcurrentBag{new UserTypeReader()}, - [typeof(IGuildUser)] = new ConcurrentBag{new UserTypeReader()}, + [typeof(IMessage)] = new MessageTypeReader(), + [typeof(IUserMessage)] = new MessageTypeReader(), + [typeof(IChannel)] = new ChannelTypeReader(), + [typeof(IDMChannel)] = new ChannelTypeReader(), + [typeof(IGroupChannel)] = new ChannelTypeReader(), + [typeof(IGuildChannel)] = new ChannelTypeReader(), + [typeof(IMessageChannel)] = new ChannelTypeReader(), + [typeof(IPrivateChannel)] = new ChannelTypeReader(), + [typeof(ITextChannel)] = new ChannelTypeReader(), + [typeof(IVoiceChannel)] = new ChannelTypeReader(), + + [typeof(IRole)] = new RoleTypeReader(), + + [typeof(IUser)] = new UserTypeReader(), + [typeof(IGroupUser)] = new UserTypeReader(), + [typeof(IGuildUser)] = new UserTypeReader(), }; _caseSensitive = config.CaseSensitiveCommands; _defaultRunMode = config.DefaultRunMode; @@ -197,21 +200,28 @@ namespace Discord.Commands //Type Readers public void AddTypeReader(TypeReader reader) { - var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentBag()); - readers.Add(reader); + var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentDictionary()); + readers[reader.GetType()] = reader; } public void AddTypeReader(Type type, TypeReader reader) { - var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentBag()); - readers.Add(reader); + var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary()); + readers[reader.GetType()] = reader; } - internal IEnumerable GetTypeReaders(Type type) + internal IDictionary GetTypeReaders(Type type) { - ConcurrentBag definedTypeReaders; + ConcurrentDictionary definedTypeReaders; if (_typeReaders.TryGetValue(type, out definedTypeReaders)) return definedTypeReaders; return null; } + internal TypeReader GetDefaultTypeReader(Type type) + { + TypeReader reader; + if (_defaultTypeReaders.TryGetValue(type, out reader)) + return reader; + return null; + } //Execution public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index a91a1f9f4..a6ac50005 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -135,26 +135,17 @@ namespace Discord.Commands if (map == null) map = DependencyMap.Empty; - object[] args = null; - try { - args = GenerateArgs(argList, paramList); - } - catch (Exception ex) - { - return ExecuteResult.FromError(ex); - } + object[] args = GenerateArgs(argList, paramList); - foreach (var parameter in Parameters) - { - var result = await parameter.CheckPreconditionsAsync(context, args, map).ConfigureAwait(false); - if (!result.IsSuccess) - return ExecuteResult.FromError(result); - } + foreach (var parameter in Parameters) + { + var result = await parameter.CheckPreconditionsAsync(context, args, map).ConfigureAwait(false); + if (!result.IsSuccess) + return ExecuteResult.FromError(result); + } - try - { switch (RunMode) { case RunMode.Sync: //Always sync