From af433c82ccf9d5de28008af1d2f7399124a2ebc1 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Tue, 15 Nov 2016 20:53:18 +0000 Subject: [PATCH] Complete builders, start work on using them --- .../Builders/CommandBuilder.cs | 30 +- .../Builders/ModuleBuilder.cs | 21 +- .../Builders/ParameterBuilder.cs | 86 ++++++ src/Discord.Net.Commands/CommandInfo.cs | 284 ------------------ src/Discord.Net.Commands/CommandParser.cs | 2 +- .../Extensions/IEnumerableExtensions.cs | 22 ++ src/Discord.Net.Commands/Info/CommandInfo.cs | 173 +++++++++++ src/Discord.Net.Commands/Info/ModuleInfo.cs | 74 +++++ .../ParameterInfo.cs} | 45 +-- src/Discord.Net.Commands/ModuleInfo.cs | 85 ------ src/Discord.Net.Commands/ReflectionUtils.cs | 2 +- 11 files changed, 416 insertions(+), 408 deletions(-) create mode 100644 src/Discord.Net.Commands/Builders/ParameterBuilder.cs delete mode 100644 src/Discord.Net.Commands/CommandInfo.cs create mode 100644 src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs create mode 100644 src/Discord.Net.Commands/Info/CommandInfo.cs create mode 100644 src/Discord.Net.Commands/Info/ModuleInfo.cs rename src/Discord.Net.Commands/{CommandParameter.cs => Info/ParameterInfo.cs} (50%) delete mode 100644 src/Discord.Net.Commands/ModuleInfo.cs diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index e14743499..077787427 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -2,14 +2,18 @@ using System.Threading.Tasks; using System.Collections.Generic; -namespace Discord.Commands +namespace Discord.Commands.Builders { public class CommandBuilder { + private List preconditions; + private List parameters; private List aliases; internal CommandBuilder(ModuleBuilder module, string prefix) { + preconditions = new List(); + parameters = new List(); aliases = new List(); if (prefix != null) @@ -21,16 +25,16 @@ namespace Discord.Commands Module = module; } - public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } - public Func Callback { get; set; } + public Func Callback { get; set; } public ModuleBuilder Module { get; } + public List Preconditions => preconditions; + public List Parameters => parameters; public List Aliases => aliases; - public CommandBuilder SetName(string name) { Name = name; @@ -49,21 +53,33 @@ namespace Discord.Commands return this; } - public CommandBuilder SetCallback(Func callback) + public CommandBuilder SetCallback(Func callback) { Callback = callback; return this; } + public CommandBuilder AddPrecondition(PreconditionAttribute precondition) + { + preconditions.Add(precondition); + return this; + } + + public CommandBuilder AddParameter(ParameterBuilder parameter) + { + parameters.Add(parameter); + return this; + } + public CommandBuilder AddAlias(string alias) { aliases.Add(alias); return this; } - public ModuleBuilder Done() + internal CommandInfo Build(ModuleInfo info, CommandService service) { - return Module; + return new CommandInfo(this, info, service); } } } \ No newline at end of file diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index ca4f84633..0375383a8 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -2,15 +2,15 @@ using System.Collections.Generic; using System.Collections.Immutable; -namespace Discord.Commands +namespace Discord.Commands.Builders { public class ModuleBuilder { private List commands; private List submodules; + private List preconditions; private List aliases; - public ModuleBuilder() : this(null, null) { } @@ -27,6 +27,7 @@ namespace Discord.Commands { commands = new List(); submodules = new List(); + preconditions = new List(); aliases = new List(); if (prefix != null) @@ -38,7 +39,6 @@ namespace Discord.Commands ParentModule = parent; } - public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } @@ -46,9 +46,9 @@ namespace Discord.Commands public List Commands => commands; public List Modules => submodules; + public List Preconditions => preconditions; public List Aliases => aliases; - public ModuleBuilder SetName(string name) { Name = name; @@ -73,6 +73,12 @@ namespace Discord.Commands return this; } + public ModuleBuilder AddPrecondition(PreconditionAttribute precondition) + { + preconditions.Add(precondition); + return this; + } + public CommandBuilder AddCommand() => AddCommand(null); public CommandBuilder AddCommand(string name) { @@ -91,12 +97,9 @@ namespace Discord.Commands return builder; } - public ModuleBuilder Done() + public ModuleInfo Build(CommandService service) { - if (ParentModule == null) - throw new InvalidOperationException("Cannot finish a top-level module!"); - - return ParentModule; + return new ModuleInfo(this, service); } } } diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs new file mode 100644 index 000000000..e90b2a9bf --- /dev/null +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Discord.Commands.Builders +{ + public class ParameterBuilder + { + public ParameterBuilder() + { } + + public ParameterBuilder(string name) + { + Name = name; + } + + public string Name { get; set; } + public string Summary { get; set; } + public object DefaultValue { get; set; } + public Type ParameterType { get; set; } + + public TypeReader TypeReader { get; set; } + + public bool Optional { get; set; } + public bool Remainder { get; set; } + public bool Multiple { get; set; } + + public ParameterBuilder SetName(string name) + { + Name = name; + return this; + } + + public ParameterBuilder SetSummary(string summary) + { + Summary = summary; + return this; + } + + public ParameterBuilder SetDefault(T defaultValue) + { + DefaultValue = defaultValue; + ParameterType = typeof(T); + + if (ParameterType.IsArray) + ParameterType = ParameterType.GetElementType(); + + return this; + } + + public ParameterBuilder SetType(Type parameterType) + { + ParameterType = parameterType; + return this; + } + + public ParameterBuilder SetTypeReader(TypeReader reader) + { + TypeReader = reader; + return this; + } + + public ParameterBuilder SetOptional(bool isOptional) + { + Optional = isOptional; + return this; + } + + public ParameterBuilder SetRemainder(bool isRemainder) + { + Remainder = isRemainder; + return this; + } + + public ParameterBuilder SetMultiple(bool isMultiple) + { + Multiple = isMultiple; + return this; + } + + internal ParameterInfo Build(CommandInfo info, CommandService service) + { + return new ParameterInfo(this, info, service); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/CommandInfo.cs b/src/Discord.Net.Commands/CommandInfo.cs deleted file mode 100644 index 47aae1ae2..000000000 --- a/src/Discord.Net.Commands/CommandInfo.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -namespace Discord.Commands -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class CommandInfo - { - private static readonly MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); - private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); - - private readonly Func _action; - - public MethodInfo Source { get; } - public ModuleInfo Module { get; } - public string Name { get; } - public string Summary { get; } - public string Remarks { get; } - public string Text { get; } - public int Priority { get; } - public bool HasVarArgs { get; } - public RunMode RunMode { get; } - public IReadOnlyList Aliases { get; } - public IReadOnlyList Parameters { get; } - public IReadOnlyList Preconditions { get; } - - internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix) - { - try - { - Source = source; - Module = module; - - Name = source.Name; - - if (attribute.Text == null) - Text = groupPrefix; - RunMode = attribute.RunMode; - - if (groupPrefix != "") - groupPrefix += " "; - - if (attribute.Text != null) - Text = groupPrefix + attribute.Text; - - var aliasesBuilder = ImmutableArray.CreateBuilder(); - - aliasesBuilder.Add(Text); - - var aliasesAttr = source.GetCustomAttribute(); - if (aliasesAttr != null) - aliasesBuilder.AddRange(aliasesAttr.Aliases.Select(x => groupPrefix + x)); - - Aliases = aliasesBuilder.ToImmutable(); - - var nameAttr = source.GetCustomAttribute(); - if (nameAttr != null) - Name = nameAttr.Text; - - var summary = source.GetCustomAttribute(); - if (summary != null) - Summary = summary.Text; - - var remarksAttr = source.GetCustomAttribute(); - if (remarksAttr != null) - Remarks = remarksAttr.Text; - - var priorityAttr = source.GetCustomAttribute(); - Priority = priorityAttr?.Priority ?? 0; - - Parameters = BuildParameters(source); - HasVarArgs = Parameters.Count > 0 ? Parameters[Parameters.Count - 1].IsMultiple : false; - Preconditions = BuildPreconditions(source); - _action = BuildAction(source); - } - catch (Exception ex) - { - throw new Exception($"Failed to build command {source.DeclaringType.FullName}.{source.Name}", ex); - } - } - - public async Task CheckPreconditions(CommandContext context, IDependencyMap map = null) - { - if (map == null) - map = DependencyMap.Empty; - - foreach (PreconditionAttribute precondition in Module.Preconditions) - { - var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); - if (!result.IsSuccess) - return result; - } - - foreach (PreconditionAttribute precondition in Preconditions) - { - var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); - if (!result.IsSuccess) - return result; - } - - return PreconditionResult.FromSuccess(); - } - - public async Task Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) - { - if (!searchResult.IsSuccess) - return ParseResult.FromError(searchResult); - if (preconditionResult != null && !preconditionResult.Value.IsSuccess) - return ParseResult.FromError(preconditionResult.Value); - - string input = searchResult.Text; - var matchingAliases = Aliases.Where(alias => input.StartsWith(alias)); - - string matchingAlias = ""; - foreach (string alias in matchingAliases) - { - if (alias.Length > matchingAlias.Length) - matchingAlias = alias; - } - - input = input.Substring(matchingAlias.Length); - - return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); - } - public Task Execute(CommandContext context, ParseResult parseResult, IDependencyMap map) - { - if (!parseResult.IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult)); - - var argList = new object[parseResult.ArgValues.Count]; - for (int i = 0; i < parseResult.ArgValues.Count; i++) - { - if (!parseResult.ArgValues[i].IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); - argList[i] = parseResult.ArgValues[i].Values.First().Value; - } - - var paramList = new object[parseResult.ParamValues.Count]; - for (int i = 0; i < parseResult.ParamValues.Count; i++) - { - if (!parseResult.ParamValues[i].IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); - paramList[i] = parseResult.ParamValues[i].Values.First().Value; - } - - return Execute(context, argList, paramList, map); - } - public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList, IDependencyMap map) - { - if (map == null) - map = DependencyMap.Empty; - - try - { - var args = GenerateArgs(argList, paramList); - switch (RunMode) - { - case RunMode.Sync: //Always sync - await _action(context, args, map).ConfigureAwait(false); - break; - case RunMode.Mixed: //Sync until first await statement - var t1 = _action(context, args, map); - break; - case RunMode.Async: //Always async - var t2 = Task.Run(() => _action(context, args, map)); - break; - } - return ExecuteResult.FromSuccess(); - } - catch (Exception ex) - { - return ExecuteResult.FromError(ex); - } - } - - private IReadOnlyList BuildPreconditions(MethodInfo methodInfo) - { - return methodInfo.GetCustomAttributes().ToImmutableArray(); - } - - private IReadOnlyList BuildParameters(MethodInfo methodInfo) - { - var parameters = methodInfo.GetParameters(); - - var paramBuilder = ImmutableArray.CreateBuilder(parameters.Length); - for (int i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var type = parameter.ParameterType; - - //Detect 'params' - bool isMultiple = parameter.GetCustomAttribute() != null; - if (isMultiple) - type = type.GetElementType(); - - var reader = Module.Service.GetTypeReader(type); - var typeInfo = type.GetTypeInfo(); - - //Detect enums - if (reader == null && typeInfo.IsEnum) - { - reader = EnumTypeReader.GetReader(type); - Module.Service.AddTypeReader(type, reader); - } - - if (reader == null) - throw new InvalidOperationException($"{type.FullName} is not supported as a command parameter, are you missing a TypeReader?"); - - bool isRemainder = parameter.GetCustomAttribute() != null; - if (isRemainder && i != parameters.Length - 1) - throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); - - string name = parameter.Name; - string summary = parameter.GetCustomAttribute()?.Text; - bool isOptional = parameter.IsOptional; - object defaultValue = parameter.HasDefaultValue ? parameter.DefaultValue : null; - - paramBuilder.Add(new CommandParameter(parameters[i], name, summary, type, reader, isOptional, isRemainder, isMultiple, defaultValue)); - } - return paramBuilder.ToImmutable(); - } - private Func BuildAction(MethodInfo methodInfo) - { - if (methodInfo.ReturnType != typeof(Task)) - throw new InvalidOperationException("Commands must return a non-generic Task."); - - return (context, args, map) => - { - var instance = Module.CreateInstance(map); - instance.Context = context; - try - { - return methodInfo.Invoke(instance, args) as Task ?? Task.CompletedTask; - } - finally - { - (instance as IDisposable)?.Dispose(); - } - }; - } - - private object[] GenerateArgs(IEnumerable argList, IEnumerable paramsList) - { - int argCount = Parameters.Count; - var array = new object[Parameters.Count]; - if (HasVarArgs) - argCount--; - - int i = 0; - foreach (var arg in argList) - { - if (i == argCount) - throw new InvalidOperationException("Command was invoked with too many parameters"); - array[i++] = arg; - } - if (i < argCount) - throw new InvalidOperationException("Command was invoked with too few parameters"); - - if (HasVarArgs) - { - var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].ElementType, t => - { - var method = _convertParamsMethod.MakeGenericMethod(t); - return (Func, object>)method.CreateDelegate(typeof(Func, object>)); - }); - array[i] = func(paramsList); - } - - return array; - } - - private static T[] ConvertParamsList(IEnumerable paramsList) - => paramsList.Cast().ToArray(); - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Module.Name}.{Name} ({Text})"; - } -} diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 1808b705d..7bdbed955 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -15,7 +15,7 @@ namespace Discord.Commands public static async Task ParseArgs(CommandInfo command, CommandContext context, string input, int startPos) { - CommandParameter curParam = null; + ParameterInfo curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); int endPos = input.Length; var curPart = ParserPart.None; diff --git a/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs b/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs new file mode 100644 index 000000000..b922dd903 --- /dev/null +++ b/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Discord.Commands +{ + public static class IEnumerableExtensions + { + public static IEnumerable Permutate( + this IEnumerable set, + IEnumerable others, + Func func) + { + foreach (TFirst elem in set) + { + foreach (TSecond elem2 in others) + { + yield return func(elem, elem2); + } + } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs new file mode 100644 index 000000000..fc1e0421b --- /dev/null +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using System.Reflection; + +using Discord.Commands.Builders; + +namespace Discord.Commands +{ + public class CommandInfo + { + 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; + + public ModuleInfo Module { get; } + public string Name { get; } + public string Summary { get; } + public string Remarks { get; } + public int Priority { get; } + public bool HasVarArgs { get; } + public RunMode RunMode { get; } + + public IReadOnlyList Aliases { get; } + public IReadOnlyList Parameters { get; } + public IReadOnlyList Preconditions { get; } + + internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) + { + Module = module; + + Name = builder.Name; + Summary = builder.Summary; + Remarks = builder.Remarks; + + Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); + Preconditions = builder.Preconditions.ToImmutableArray(); + + Parameters = builder.Parameters.Select(x => x.Build(this, service)).ToImmutableArray(); + + _action = builder.Callback; + } + + public async Task CheckPreconditions(CommandContext context, IDependencyMap map = null) + { + if (map == null) + map = DependencyMap.Empty; + + foreach (PreconditionAttribute precondition in Module.Preconditions) + { + var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + foreach (PreconditionAttribute precondition in Preconditions) + { + var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + return PreconditionResult.FromSuccess(); + } + + public async Task Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) + { + if (!searchResult.IsSuccess) + return ParseResult.FromError(searchResult); + if (preconditionResult != null && !preconditionResult.Value.IsSuccess) + return ParseResult.FromError(preconditionResult.Value); + + string input = searchResult.Text; + var matchingAliases = Aliases.Where(alias => input.StartsWith(alias)); + + string matchingAlias = ""; + foreach (string alias in matchingAliases) + { + if (alias.Length > matchingAlias.Length) + matchingAlias = alias; + } + + input = input.Substring(matchingAlias.Length); + + return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); + } + + public Task Execute(CommandContext context, ParseResult parseResult) + { + if (!parseResult.IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult)); + + var argList = new object[parseResult.ArgValues.Count]; + for (int i = 0; i < parseResult.ArgValues.Count; i++) + { + if (!parseResult.ArgValues[i].IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); + argList[i] = parseResult.ArgValues[i].Values.First().Value; + } + + var paramList = new object[parseResult.ParamValues.Count]; + for (int i = 0; i < parseResult.ParamValues.Count; i++) + { + if (!parseResult.ParamValues[i].IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); + paramList[i] = parseResult.ParamValues[i].Values.First().Value; + } + + return Execute(context, argList, paramList); + } + public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList) + { + try + { + var args = GenerateArgs(argList, paramList); + switch (RunMode) + { + case RunMode.Sync: //Always sync + await _action(context, args).ConfigureAwait(false); + break; + case RunMode.Mixed: //Sync until first await statement + var t1 = _action(context, args); + break; + case RunMode.Async: //Always async + var t2 = Task.Run(() => _action(context, args)); + break; + } + return ExecuteResult.FromSuccess(); + } + catch (Exception ex) + { + return ExecuteResult.FromError(ex); + } + } + + private object[] GenerateArgs(IEnumerable argList, IEnumerable paramsList) + { + int argCount = Parameters.Count; + var array = new object[Parameters.Count]; + if (HasVarArgs) + argCount--; + + int i = 0; + foreach (var arg in argList) + { + if (i == argCount) + throw new InvalidOperationException("Command was invoked with too many parameters"); + array[i++] = arg; + } + if (i < argCount) + throw new InvalidOperationException("Command was invoked with too few parameters"); + + if (HasVarArgs) + { + var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].ParameterType, t => + { + var method = _convertParamsMethod.MakeGenericMethod(t); + return (Func, object>)method.CreateDelegate(typeof(Func, object>)); + }); + array[i] = func(paramsList); + } + + return array; + } + + private static T[] ConvertParamsList(IEnumerable paramsList) + => paramsList.Cast().ToArray(); + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs new file mode 100644 index 000000000..33a6574c4 --- /dev/null +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -0,0 +1,74 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; + +using Discord.Commands.Builders; + +namespace Discord.Commands +{ + public class ModuleInfo + { + public CommandService Service { get; } + public string Name { get; } + public string Summary { get; } + public string Remarks { get; } + + public IReadOnlyList Aliases { get; } + public IEnumerable Commands { get; } + public IReadOnlyList Preconditions { get; } + + internal ModuleInfo(ModuleBuilder builder, CommandService service) + { + Service = service; + + Name = builder.Name; + Summary = builder.Summary; + Remarks = builder.Remarks; + + Aliases = BuildAliases(builder).ToImmutableArray(); + Commands = builder.Commands.Select(x => x.Build(this, service)); + Preconditions = BuildPreconditions(builder).ToImmutableArray(); + } + + private static List BuildAliases(ModuleBuilder builder) + { + IEnumerable result = null; + + Stack builderStack = new Stack(); + + ModuleBuilder parent = builder; + while (parent.ParentModule != null) + { + builderStack.Push(parent); + parent = parent.ParentModule; + } + + while (builderStack.Count() > 0) + { + ModuleBuilder level = builderStack.Pop(); // get the topmost builder + if (result == null) + result = level.Aliases.ToList(); // create a shallow copy so we don't overwrite the builder unexpectedly + else + result = result.Permutate(level.Aliases, (first, second) => first + " " + second); + } + + return result.ToList(); + } + + private static List BuildPreconditions(ModuleBuilder builder) + { + var result = new List(); + + + ModuleBuilder parent = builder; + while (parent.ParentModule != null) + { + result.AddRange(parent.Preconditions); + parent = parent.ParentModule; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/CommandParameter.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs similarity index 50% rename from src/Discord.Net.Commands/CommandParameter.cs rename to src/Discord.Net.Commands/Info/ParameterInfo.cs index 1edf42bf1..812fec572 100644 --- a/src/Discord.Net.Commands/CommandParameter.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -1,37 +1,40 @@ -using System; -using System.Diagnostics; -using System.Reflection; +using System; +using System.Linq; using System.Threading.Tasks; +using Discord.Commands.Builders; + namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class CommandParameter + public class ParameterInfo { private readonly TypeReader _reader; - public ParameterInfo Source { get; } + internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) + { + Command = command; + + Name = builder.Name; + Summary = builder.Summary; + IsOptional = builder.Optional; + IsRemainder = builder.Remainder; + IsMultiple = builder.Multiple; + + ParameterType = builder.ParameterType; + DefaultValue = builder.DefaultValue; + + _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 ElementType { get; } + public Type ParameterType { get; } public object DefaultValue { get; } - public CommandParameter(ParameterInfo source, string name, string summary, Type type, TypeReader reader, bool isOptional, bool isRemainder, bool isMultiple, object defaultValue) - { - Source = source; - Name = name; - Summary = summary; - ElementType = type; - _reader = reader; - IsOptional = isOptional; - IsRemainder = isRemainder; - IsMultiple = isMultiple; - DefaultValue = defaultValue; - } - public async Task Parse(CommandContext context, string input) { return await _reader.Read(context, input).ConfigureAwait(false); @@ -40,4 +43,4 @@ namespace Discord.Commands public override string ToString() => Name; private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}"; } -} +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/ModuleInfo.cs b/src/Discord.Net.Commands/ModuleInfo.cs deleted file mode 100644 index b7471edb5..000000000 --- a/src/Discord.Net.Commands/ModuleInfo.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Reflection; - -namespace Discord.Commands -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class ModuleInfo - { - internal readonly Func _builder; - - public TypeInfo Source { get; } - public CommandService Service { get; } - public string Name { get; } - public string Prefix { get; } - public string Summary { get; } - public string Remarks { get; } - public IEnumerable Commands { get; } - public IReadOnlyList Preconditions { get; } - - internal ModuleInfo(TypeInfo source, CommandService service) - { - Source = source; - Service = service; - Name = source.Name; - _builder = ReflectionUtils.CreateBuilder(source, Service); - - var groupAttr = source.GetCustomAttribute(); - if (groupAttr != null) - Prefix = groupAttr.Prefix; - else - Prefix = ""; - - var nameAttr = source.GetCustomAttribute(); - if (nameAttr != null) - Name = nameAttr.Text; - - var summaryAttr = source.GetCustomAttribute(); - if (summaryAttr != null) - Summary = summaryAttr.Text; - - var remarksAttr = source.GetCustomAttribute(); - if (remarksAttr != null) - Remarks = remarksAttr.Text; - - List commands = new List(); - SearchClass(source, commands, Prefix); - Commands = commands; - - Preconditions = Source.GetCustomAttributes().ToImmutableArray(); - } - private void SearchClass(TypeInfo parentType, List commands, string groupPrefix) - { - foreach (var method in parentType.DeclaredMethods) - { - var cmdAttr = method.GetCustomAttribute(); - if (cmdAttr != null) - commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix)); - } - foreach (var type in parentType.DeclaredNestedTypes) - { - var groupAttrib = type.GetCustomAttribute(); - if (groupAttrib != null) - { - string nextGroupPrefix; - - if (groupPrefix != "") - nextGroupPrefix = groupPrefix + " " + (groupAttrib.Prefix ?? type.Name.ToLowerInvariant()); - else - nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); - - SearchClass(type, commands, nextGroupPrefix); - } - } - } - - internal ModuleBase CreateInstance(IDependencyMap map) - => _builder(map); - - public override string ToString() => Name; - private string DebuggerDisplay => Name; - } -} diff --git a/src/Discord.Net.Commands/ReflectionUtils.cs b/src/Discord.Net.Commands/ReflectionUtils.cs index 052e5fe98..27ea601bf 100644 --- a/src/Discord.Net.Commands/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/ReflectionUtils.cs @@ -18,7 +18,7 @@ namespace Discord.Commands throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); var constructor = constructors[0]; - ParameterInfo[] parameters = constructor.GetParameters(); + System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); return (map) => {