|
|
@@ -0,0 +1,191 @@ |
|
|
|
using System; |
|
|
|
using System.Collections; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Collections.Immutable; |
|
|
|
using System.Linq; |
|
|
|
using System.Reflection; |
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
|
|
namespace Discord.Commands |
|
|
|
{ |
|
|
|
internal sealed class NamedArgumentTypeReader<T> : TypeReader |
|
|
|
where T : class, new() |
|
|
|
{ |
|
|
|
private static readonly IReadOnlyDictionary<string, PropertyInfo> _tProps = typeof(T).GetTypeInfo().DeclaredProperties |
|
|
|
.Where(p => p.SetMethod != null && p.SetMethod.IsPublic && !p.SetMethod.IsStatic) |
|
|
|
.ToImmutableDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); |
|
|
|
|
|
|
|
private readonly CommandService _commands; |
|
|
|
|
|
|
|
public NamedArgumentTypeReader(CommandService commands) |
|
|
|
{ |
|
|
|
_commands = commands; |
|
|
|
} |
|
|
|
|
|
|
|
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) |
|
|
|
{ |
|
|
|
var result = new T(); |
|
|
|
var state = ReadState.LookingForParameter; |
|
|
|
int beginRead = 0, currentRead = 0; |
|
|
|
|
|
|
|
while (state != ReadState.End) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
var prop = Read(out var arg); |
|
|
|
var propVal = await ReadArgumentAsync(prop, arg).ConfigureAwait(false); |
|
|
|
if (propVal != null) |
|
|
|
prop.SetMethod.Invoke(result, new[] { propVal }); |
|
|
|
else |
|
|
|
return TypeReaderResult.FromError(CommandError.ParseFailed, $"Could not parse the argument for the parameter '{prop.Name}' as type '{prop.PropertyType}'."); |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
//TODO: use the Exception overload after a rebase on latest |
|
|
|
return TypeReaderResult.FromError(CommandError.Exception, ex.Message); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return TypeReaderResult.FromSuccess(result); |
|
|
|
|
|
|
|
PropertyInfo Read(out string arg) |
|
|
|
{ |
|
|
|
string currentParam = null; |
|
|
|
char match = '\0'; |
|
|
|
|
|
|
|
for (; currentRead < input.Length; currentRead++) |
|
|
|
{ |
|
|
|
var currentChar = input[currentRead]; |
|
|
|
switch (state) |
|
|
|
{ |
|
|
|
case ReadState.LookingForParameter: |
|
|
|
if (Char.IsWhiteSpace(currentChar)) |
|
|
|
continue; |
|
|
|
else |
|
|
|
{ |
|
|
|
beginRead = currentRead; |
|
|
|
state = ReadState.InParameter; |
|
|
|
} |
|
|
|
break; |
|
|
|
case ReadState.InParameter: |
|
|
|
if (currentChar != ':') |
|
|
|
continue; |
|
|
|
else |
|
|
|
{ |
|
|
|
currentParam = input.Substring(beginRead, currentRead - beginRead); |
|
|
|
state = ReadState.LookingForArgument; |
|
|
|
} |
|
|
|
break; |
|
|
|
case ReadState.LookingForArgument: |
|
|
|
if (Char.IsWhiteSpace(currentChar)) |
|
|
|
continue; |
|
|
|
else |
|
|
|
{ |
|
|
|
beginRead = currentRead; |
|
|
|
state = (QuotationAliasUtils.GetDefaultAliasMap.TryGetValue(currentChar, out match)) |
|
|
|
? ReadState.InQuotedArgument |
|
|
|
: ReadState.InArgument; |
|
|
|
} |
|
|
|
break; |
|
|
|
case ReadState.InArgument: |
|
|
|
if (!Char.IsWhiteSpace(currentChar)) |
|
|
|
continue; |
|
|
|
else |
|
|
|
return GetPropAndValue(out arg); |
|
|
|
case ReadState.InQuotedArgument: |
|
|
|
if (currentChar != match) |
|
|
|
continue; |
|
|
|
else |
|
|
|
return GetPropAndValue(out arg); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (currentParam == null) |
|
|
|
throw new InvalidOperationException("No parameter name was read."); |
|
|
|
|
|
|
|
return GetPropAndValue(out arg); |
|
|
|
|
|
|
|
PropertyInfo GetPropAndValue(out string argv) |
|
|
|
{ |
|
|
|
bool quoted = state == ReadState.InQuotedArgument; |
|
|
|
state = (currentRead == (quoted ? input.Length - 1 : input.Length)) |
|
|
|
? ReadState.End |
|
|
|
: ReadState.LookingForParameter; |
|
|
|
|
|
|
|
if (quoted) |
|
|
|
{ |
|
|
|
argv = input.Substring(beginRead + 1, currentRead - beginRead - 1).Trim(); |
|
|
|
currentRead++; |
|
|
|
} |
|
|
|
else |
|
|
|
argv = input.Substring(beginRead, currentRead - beginRead); |
|
|
|
|
|
|
|
return _tProps[currentParam]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async Task<object> 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<OverrideTypeReaderAttribute>(); |
|
|
|
var reader = (overridden != null) |
|
|
|
? ModuleClassBuilder.GetTypeReader(_commands, elemType, overridden.TypeReader, services) |
|
|
|
: (_commands.GetDefaultTypeReader(elemType) |
|
|
|
?? _commands.GetTypeReaders(elemType).FirstOrDefault().Value); |
|
|
|
|
|
|
|
if (reader != null) |
|
|
|
{ |
|
|
|
if (isCollection) |
|
|
|
{ |
|
|
|
var method = _readMultipleMethod.MakeGenericMethod(elemType); |
|
|
|
var task = (Task<IEnumerable>)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<object> 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<IEnumerable> ReadMultiple<TObj>(TypeReader reader, ICommandContext context, IEnumerable<string> args, IServiceProvider services) |
|
|
|
{ |
|
|
|
var objs = new List<TObj>(); |
|
|
|
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<T>) |
|
|
|
.GetTypeInfo() |
|
|
|
.DeclaredMethods |
|
|
|
.Single(m => m.IsPrivate && m.IsStatic && m.Name == nameof(ReadMultiple)); |
|
|
|
|
|
|
|
private enum ReadState |
|
|
|
{ |
|
|
|
LookingForParameter, |
|
|
|
InParameter, |
|
|
|
LookingForArgument, |
|
|
|
InArgument, |
|
|
|
InQuotedArgument, |
|
|
|
End |
|
|
|
} |
|
|
|
} |
|
|
|
} |