You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
7.8 KiB
191 lines
7.8 KiB
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)
|
|
{
|
|
return TypeReaderResult.FromError(ex);
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|