diff --git a/src/Discord.Net.Commands/Readers/NamedArgumentTypeReader.cs b/src/Discord.Net.Commands/Readers/NamedArgumentTypeReader.cs index be4b2765b..01559293f 100644 --- a/src/Discord.Net.Commands/Readers/NamedArgumentTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/NamedArgumentTypeReader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -125,23 +126,58 @@ namespace Discord.Commands async Task ReadArgumentAsync(PropertyInfo prop, string arg) { + var elemType = prop.PropertyType; + bool isCollection = false; + if (elemType.GetTypeInfo().IsGenericType && elemType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + elemType = prop.PropertyType.GenericTypeArguments[0]; + isCollection = true; + } + var overridden = prop.GetCustomAttribute(); var reader = (overridden != null) - ? ModuleClassBuilder.GetTypeReader(_commands, prop.PropertyType, overridden.TypeReader, services) - : (_commands.GetDefaultTypeReader(prop.PropertyType) - ?? _commands.GetTypeReaders(prop.PropertyType).FirstOrDefault().Value); + ? ModuleClassBuilder.GetTypeReader(_commands, elemType, overridden.TypeReader, services) + : (_commands.GetDefaultTypeReader(elemType) + ?? _commands.GetTypeReaders(elemType).FirstOrDefault().Value); if (reader != null) { - var readResult = await reader.ReadAsync(context, arg, services).ConfigureAwait(false); - return (readResult.IsSuccess) - ? readResult.BestMatch - : null; + if (isCollection) + { + var method = _readMultipleMethod.MakeGenericMethod(elemType); + var task = (Task)method.Invoke(null, new object[] { reader, context, arg.Split(','), services }); + return await task.ConfigureAwait(false); + } + else + return await ReadSingle(reader, context, arg, services).ConfigureAwait(false); } return null; } } + private static async Task ReadSingle(TypeReader reader, ICommandContext context, string arg, IServiceProvider services) + { + var readResult = await reader.ReadAsync(context, arg, services).ConfigureAwait(false); + return (readResult.IsSuccess) + ? readResult.BestMatch + : null; + } + private static async Task ReadMultiple(TypeReader reader, ICommandContext context, IEnumerable args, IServiceProvider services) + { + var objs = new List(); + foreach (var arg in args) + { + var read = await ReadSingle(reader, context, arg.Trim(), services).ConfigureAwait(false); + if (read != null) + objs.Add((TObj)read); + } + return objs.ToImmutableArray(); + } + private static readonly MethodInfo _readMultipleMethod = typeof(NamedArgumentTypeReader) + .GetTypeInfo() + .DeclaredMethods + .Single(m => m.IsPrivate && m.IsStatic && m.Name == nameof(ReadMultiple)); + private enum ReadState { LookingForParameter, diff --git a/test/Discord.Net.Tests/Tests.TypeReaders.cs b/test/Discord.Net.Tests/Tests.TypeReaders.cs index 884f8d511..63083bc6b 100644 --- a/test/Discord.Net.Tests/Tests.TypeReaders.cs +++ b/test/Discord.Net.Tests/Tests.TypeReaders.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Discord.Commands; using Xunit; @@ -80,6 +81,27 @@ namespace Discord Assert.False(result.IsSuccess); Assert.Equal(expected: CommandError.Exception, actual: result.Error); } + + [Fact] + public async Task TestMultiple() + { + var commands = new CommandService(); + var module = await commands.AddModuleAsync(null); + + Assert.NotNull(module); + Assert.NotEmpty(module.Commands); + + var cmd = module.Commands[0]; + Assert.NotNull(cmd); + Assert.NotEmpty(cmd.Parameters); + + var param = cmd.Parameters[0]; + Assert.NotNull(param); + Assert.True(param.IsRemainder); + + var result = await param.ParseAsync(null, "manyints: \"1, 2, 3, 4, 5, 6, 7\""); + Assert.True(result.IsSuccess); + } } [NamedArgumentType] @@ -89,6 +111,8 @@ namespace Discord [OverrideTypeReader(typeof(CustomTypeReader))] public string Bar { get; set; } + + public IEnumerable ManyInts { get; set; } } public sealed class CustomTypeReader : TypeReader