| @@ -0,0 +1,36 @@ | |||||
| using System; | |||||
| using System.Reflection; | |||||
| namespace Discord.Commands | |||||
| { | |||||
| /// <summary> | |||||
| /// Allows to override the <see cref="TypeReader"/> used for a parameter/type. | |||||
| /// </summary> | |||||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = true)] | |||||
| public class TypeReaderAttribute : Attribute | |||||
| { | |||||
| /// <summary> | |||||
| /// Type of the type that is read. | |||||
| /// </summary> | |||||
| public Type Type { get; } | |||||
| /// <summary> | |||||
| /// <see cref="TypeInfo"/> of the specified <see cref="TypeReader"/>. | |||||
| /// </summary> | |||||
| public TypeInfo OverridingTypeReader { get; } | |||||
| /// <summary> | |||||
| /// Specify a <see cref="TypeReader"/> for a particular type. | |||||
| /// </summary> | |||||
| /// <param name="forType">Type of the type to read.</param> | |||||
| /// <param name="typeReader">Type of the <see cref="TypeReader"/> that reads the type.</param> | |||||
| public TypeReaderAttribute(Type forType, Type typeReader) | |||||
| { | |||||
| if (!typeof(TypeReader).GetTypeInfo().IsAssignableFrom(typeReader.GetTypeInfo())) | |||||
| throw new ArgumentException($"Type of argument {nameof(typeReader)} must derive from {nameof(TypeReader)}", nameof(typeReader)); | |||||
| Type = forType; | |||||
| OverridingTypeReader = typeReader.GetTypeInfo(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -30,7 +30,7 @@ namespace Discord.Commands | |||||
| public IReadOnlyList<CommandParameter> Parameters { get; } | public IReadOnlyList<CommandParameter> Parameters { get; } | ||||
| public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | ||||
| internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix) | |||||
| internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix, IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -74,7 +74,7 @@ namespace Discord.Commands | |||||
| var priorityAttr = source.GetCustomAttribute<PriorityAttribute>(); | var priorityAttr = source.GetCustomAttribute<PriorityAttribute>(); | ||||
| Priority = priorityAttr?.Priority ?? 0; | Priority = priorityAttr?.Priority ?? 0; | ||||
| Parameters = BuildParameters(source); | |||||
| Parameters = BuildParameters(source, dependencyMap); | |||||
| HasVarArgs = Parameters.Count > 0 ? Parameters[Parameters.Count - 1].IsMultiple : false; | HasVarArgs = Parameters.Count > 0 ? Parameters[Parameters.Count - 1].IsMultiple : false; | ||||
| Preconditions = BuildPreconditions(source); | Preconditions = BuildPreconditions(source); | ||||
| _action = BuildAction(source); | _action = BuildAction(source); | ||||
| @@ -184,7 +184,7 @@ namespace Discord.Commands | |||||
| return methodInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray(); | return methodInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray(); | ||||
| } | } | ||||
| private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo) | |||||
| private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo, IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| var parameters = methodInfo.GetParameters(); | var parameters = methodInfo.GetParameters(); | ||||
| @@ -199,7 +199,13 @@ namespace Discord.Commands | |||||
| if (isMultiple) | if (isMultiple) | ||||
| type = type.GetElementType(); | type = type.GetElementType(); | ||||
| var reader = Module.Service.GetTypeReader(type); | |||||
| var trAttr = parameter.GetCustomAttribute<TypeReaderAttribute>(); | |||||
| var reader = (trAttr != null && trAttr.Type == type) ? | |||||
| ReflectionUtils.CreateObject<TypeReader>(trAttr.OverridingTypeReader, Module.Service, dependencyMap) : | |||||
| (Module.OverridingTypeReaders.ContainsKey(type) ? | |||||
| Module.OverridingTypeReaders[type] : | |||||
| Module.Service.GetTypeReader(type)); | |||||
| var typeInfo = type.GetTypeInfo(); | var typeInfo = type.GetTypeInfo(); | ||||
| //Detect enums | //Detect enums | ||||
| @@ -65,7 +65,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| //Modules | //Modules | ||||
| public async Task<ModuleInfo> AddModule<T>() | |||||
| public async Task<ModuleInfo> AddModule<T>(IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| @@ -80,14 +80,14 @@ namespace Discord.Commands | |||||
| if (_moduleDefs.ContainsKey(typeof(T))) | if (_moduleDefs.ContainsKey(typeof(T))) | ||||
| throw new ArgumentException($"This module has already been added."); | throw new ArgumentException($"This module has already been added."); | ||||
| return AddModuleInternal(typeInfo); | |||||
| return AddModuleInternal(typeInfo, dependencyMap); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _moduleLock.Release(); | _moduleLock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| public async Task<IEnumerable<ModuleInfo>> AddModules(Assembly assembly) | |||||
| public async Task<IEnumerable<ModuleInfo>> AddModules(Assembly assembly, IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| var moduleDefs = ImmutableArray.CreateBuilder<ModuleInfo>(); | var moduleDefs = ImmutableArray.CreateBuilder<ModuleInfo>(); | ||||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
| @@ -102,7 +102,7 @@ namespace Discord.Commands | |||||
| { | { | ||||
| var dontAutoLoad = typeInfo.GetCustomAttribute<DontAutoLoadAttribute>(); | var dontAutoLoad = typeInfo.GetCustomAttribute<DontAutoLoadAttribute>(); | ||||
| if (dontAutoLoad == null && !typeInfo.IsAbstract) | if (dontAutoLoad == null && !typeInfo.IsAbstract) | ||||
| moduleDefs.Add(AddModuleInternal(typeInfo)); | |||||
| moduleDefs.Add(AddModuleInternal(typeInfo, dependencyMap)); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -113,9 +113,9 @@ namespace Discord.Commands | |||||
| _moduleLock.Release(); | _moduleLock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| private ModuleInfo AddModuleInternal(TypeInfo typeInfo) | |||||
| private ModuleInfo AddModuleInternal(TypeInfo typeInfo, IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| var moduleDef = new ModuleInfo(typeInfo, this); | |||||
| var moduleDef = new ModuleInfo(typeInfo, this, dependencyMap); | |||||
| _moduleDefs[typeInfo.AsType()] = moduleDef; | _moduleDefs[typeInfo.AsType()] = moduleDef; | ||||
| foreach (var cmd in moduleDef.Commands) | foreach (var cmd in moduleDef.Commands) | ||||
| @@ -19,8 +19,9 @@ namespace Discord.Commands | |||||
| public string Remarks { get; } | public string Remarks { get; } | ||||
| public IEnumerable<CommandInfo> Commands { get; } | public IEnumerable<CommandInfo> Commands { get; } | ||||
| public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | ||||
| public ImmutableDictionary<Type, TypeReader> OverridingTypeReaders { get; } | |||||
| internal ModuleInfo(TypeInfo source, CommandService service) | |||||
| internal ModuleInfo(TypeInfo source, CommandService service, IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| Source = source; | Source = source; | ||||
| Service = service; | Service = service; | ||||
| @@ -45,19 +46,31 @@ namespace Discord.Commands | |||||
| if (remarksAttr != null) | if (remarksAttr != null) | ||||
| Remarks = remarksAttr.Text; | Remarks = remarksAttr.Text; | ||||
| var typeReaders = new Dictionary<Type, TypeReader>(); | |||||
| var trAttrs = source.GetCustomAttributes<TypeReaderAttribute>(); | |||||
| foreach (var trAttr in trAttrs) | |||||
| typeReaders[trAttr.Type] = GetOverridingTypeReader(trAttr, dependencyMap); | |||||
| OverridingTypeReaders = typeReaders.ToImmutableDictionary(); | |||||
| List<CommandInfo> commands = new List<CommandInfo>(); | List<CommandInfo> commands = new List<CommandInfo>(); | ||||
| SearchClass(source, commands, Prefix); | |||||
| SearchClass(source, commands, Prefix, dependencyMap); | |||||
| Commands = commands; | Commands = commands; | ||||
| Preconditions = Source.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray(); | Preconditions = Source.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray(); | ||||
| } | } | ||||
| private void SearchClass(TypeInfo parentType, List<CommandInfo> commands, string groupPrefix) | |||||
| private TypeReader GetOverridingTypeReader(TypeReaderAttribute trAttr, IDependencyMap dependencyMap = null) | |||||
| => ReflectionUtils.CreateObject<TypeReader>(trAttr.OverridingTypeReader, Service, dependencyMap); | |||||
| private void SearchClass(TypeInfo parentType, List<CommandInfo> commands, string groupPrefix, IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| foreach (var method in parentType.DeclaredMethods) | foreach (var method in parentType.DeclaredMethods) | ||||
| { | { | ||||
| var cmdAttr = method.GetCustomAttribute<CommandAttribute>(); | var cmdAttr = method.GetCustomAttribute<CommandAttribute>(); | ||||
| if (cmdAttr != null) | if (cmdAttr != null) | ||||
| commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix)); | |||||
| commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix, dependencyMap)); | |||||
| } | } | ||||
| foreach (var type in parentType.DeclaredNestedTypes) | foreach (var type in parentType.DeclaredNestedTypes) | ||||
| { | { | ||||
| @@ -71,7 +84,7 @@ namespace Discord.Commands | |||||
| else | else | ||||
| nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); | nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); | ||||
| SearchClass(type, commands, nextGroupPrefix); | |||||
| SearchClass(type, commands, nextGroupPrefix, dependencyMap); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||