Merge pull request #364 from FiniteReality/feature/parameter-tweaks
Parameter preconditions and typereader overriding
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
public class OverrideTypeReaderAttribute : Attribute
|
||||||
|
{
|
||||||
|
private static readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo();
|
||||||
|
|
||||||
|
public Type TypeReader { get; }
|
||||||
|
|
||||||
|
public OverrideTypeReaderAttribute(Type overridenTypeReader)
|
||||||
|
{
|
||||||
|
if (!_typeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo()))
|
||||||
|
throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}");
|
||||||
|
|
||||||
|
TypeReader = overridenTypeReader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)]
|
||||||
|
public abstract class ParameterPreconditionAttribute : Attribute
|
||||||
|
{
|
||||||
|
public abstract Task<PreconditionResult> CheckPermissions(CommandContext context, ParameterInfo parameter, object value, IDependencyMap map);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -182,6 +182,10 @@ namespace Discord.Commands
|
|||||||
// TODO: C#7 type switch
|
// TODO: C#7 type switch
|
||||||
if (attribute is SummaryAttribute)
|
if (attribute is SummaryAttribute)
|
||||||
builder.Summary = (attribute as SummaryAttribute).Text;
|
builder.Summary = (attribute as SummaryAttribute).Text;
|
||||||
|
else if (attribute is OverrideTypeReaderAttribute)
|
||||||
|
builder.TypeReader = GetTypeReader(service, paramType, (attribute as OverrideTypeReaderAttribute).TypeReader);
|
||||||
|
else if (attribute is ParameterPreconditionAttribute)
|
||||||
|
builder.AddPrecondition(attribute as ParameterPreconditionAttribute);
|
||||||
else if (attribute is ParamArrayAttribute)
|
else if (attribute is ParamArrayAttribute)
|
||||||
{
|
{
|
||||||
builder.IsMultiple = true;
|
builder.IsMultiple = true;
|
||||||
@@ -196,23 +200,42 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var reader = service.GetTypeReader(paramType);
|
if (builder.TypeReader == null)
|
||||||
if (reader == null)
|
|
||||||
{
|
{
|
||||||
var paramTypeInfo = paramType.GetTypeInfo();
|
var reader = service.GetDefaultTypeReader(paramType);
|
||||||
if (paramTypeInfo.IsEnum)
|
|
||||||
{
|
|
||||||
reader = EnumTypeReader.GetReader(paramType);
|
|
||||||
service.AddTypeReader(paramType, reader);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.ParameterType = paramType;
|
if (reader == null)
|
||||||
builder.TypeReader = reader;
|
{
|
||||||
|
var paramTypeInfo = paramType.GetTypeInfo();
|
||||||
|
if (paramTypeInfo.IsEnum)
|
||||||
|
{
|
||||||
|
reader = EnumTypeReader.GetReader(paramType);
|
||||||
|
service.AddTypeReader(paramType, reader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.ParameterType = paramType;
|
||||||
|
builder.TypeReader = reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType)
|
||||||
|
{
|
||||||
|
var readers = service.GetTypeReaders(paramType);
|
||||||
|
TypeReader reader = null;
|
||||||
|
if (readers != null)
|
||||||
|
if (readers.TryGetValue(typeReaderType, out reader))
|
||||||
|
return reader;
|
||||||
|
|
||||||
|
//could not find any registered type reader: try to create one
|
||||||
|
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty);
|
||||||
|
service.AddTypeReader(paramType, reader);
|
||||||
|
|
||||||
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidModuleDefinition(TypeInfo typeInfo)
|
private static bool IsValidModuleDefinition(TypeInfo typeInfo)
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Discord.Commands.Builders
|
namespace Discord.Commands.Builders
|
||||||
{
|
{
|
||||||
public class ParameterBuilder
|
public class ParameterBuilder
|
||||||
{
|
{
|
||||||
|
private readonly List<ParameterPreconditionAttribute> _preconditions;
|
||||||
|
|
||||||
public CommandBuilder Command { get; }
|
public CommandBuilder Command { get; }
|
||||||
public string Name { get; internal set; }
|
public string Name { get; internal set; }
|
||||||
public Type ParameterType { get; internal set; }
|
public Type ParameterType { get; internal set; }
|
||||||
@@ -16,16 +21,20 @@ namespace Discord.Commands.Builders
|
|||||||
public object DefaultValue { get; set; }
|
public object DefaultValue { get; set; }
|
||||||
public string Summary { get; set; }
|
public string Summary { get; set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<ParameterPreconditionAttribute> Preconditions => _preconditions;
|
||||||
|
|
||||||
//Automatic
|
//Automatic
|
||||||
internal ParameterBuilder(CommandBuilder command)
|
internal ParameterBuilder(CommandBuilder command)
|
||||||
{
|
{
|
||||||
|
_preconditions = new List<ParameterPreconditionAttribute>();
|
||||||
|
|
||||||
Command = command;
|
Command = command;
|
||||||
}
|
}
|
||||||
//User-defined
|
//User-defined
|
||||||
internal ParameterBuilder(CommandBuilder command, string name, Type type)
|
internal ParameterBuilder(CommandBuilder command, string name, Type type)
|
||||||
: this(command)
|
: this(command)
|
||||||
{
|
{
|
||||||
Preconditions.NotNull(name, nameof(name));
|
Discord.Preconditions.NotNull(name, nameof(name));
|
||||||
|
|
||||||
Name = name;
|
Name = name;
|
||||||
SetType(type);
|
SetType(type);
|
||||||
@@ -33,7 +42,11 @@ namespace Discord.Commands.Builders
|
|||||||
|
|
||||||
internal void SetType(Type type)
|
internal void SetType(Type type)
|
||||||
{
|
{
|
||||||
TypeReader = Command.Module.Service.GetTypeReader(type);
|
var readers = Command.Module.Service.GetTypeReaders(type);
|
||||||
|
if (readers == null)
|
||||||
|
throw new InvalidOperationException($"{type} does not have a TypeReader registered for it");
|
||||||
|
|
||||||
|
TypeReader = readers.FirstOrDefault().Value;
|
||||||
|
|
||||||
if (type.GetTypeInfo().IsValueType)
|
if (type.GetTypeInfo().IsValueType)
|
||||||
DefaultValue = Activator.CreateInstance(type);
|
DefaultValue = Activator.CreateInstance(type);
|
||||||
@@ -68,6 +81,12 @@ namespace Discord.Commands.Builders
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ParameterBuilder AddPrecondition(ParameterPreconditionAttribute precondition)
|
||||||
|
{
|
||||||
|
_preconditions.Add(precondition);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
internal ParameterInfo Build(CommandInfo info)
|
internal ParameterInfo Build(CommandInfo info)
|
||||||
{
|
{
|
||||||
if (TypeReader == null)
|
if (TypeReader == null)
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _moduleLock;
|
private readonly SemaphoreSlim _moduleLock;
|
||||||
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs;
|
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs;
|
||||||
private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders;
|
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>> _typeReaders;
|
||||||
|
private readonly ConcurrentDictionary<Type, TypeReader> _defaultTypeReaders;
|
||||||
private readonly ConcurrentBag<ModuleInfo> _moduleDefs;
|
private readonly ConcurrentBag<ModuleInfo> _moduleDefs;
|
||||||
private readonly CommandMap _map;
|
private readonly CommandMap _map;
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x);
|
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x);
|
||||||
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands);
|
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands);
|
||||||
|
public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new {y.Key, y.Value})).ToLookup(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
public CommandService() : this(new CommandServiceConfig()) { }
|
public CommandService() : this(new CommandServiceConfig()) { }
|
||||||
public CommandService(CommandServiceConfig config)
|
public CommandService(CommandServiceConfig config)
|
||||||
@@ -32,7 +34,9 @@ namespace Discord.Commands
|
|||||||
_typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>();
|
_typedModuleDefs = new ConcurrentDictionary<Type, ModuleInfo>();
|
||||||
_moduleDefs = new ConcurrentBag<ModuleInfo>();
|
_moduleDefs = new ConcurrentBag<ModuleInfo>();
|
||||||
_map = new CommandMap();
|
_map = new CommandMap();
|
||||||
_typeReaders = new ConcurrentDictionary<Type, TypeReader>
|
_typeReaders = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, TypeReader>>();
|
||||||
|
|
||||||
|
_defaultTypeReaders = new ConcurrentDictionary<Type, TypeReader>
|
||||||
{
|
{
|
||||||
[typeof(bool)] = new SimpleTypeReader<bool>(),
|
[typeof(bool)] = new SimpleTypeReader<bool>(),
|
||||||
[typeof(char)] = new SimpleTypeReader<char>(),
|
[typeof(char)] = new SimpleTypeReader<char>(),
|
||||||
@@ -50,7 +54,7 @@ namespace Discord.Commands
|
|||||||
[typeof(decimal)] = new SimpleTypeReader<decimal>(),
|
[typeof(decimal)] = new SimpleTypeReader<decimal>(),
|
||||||
[typeof(DateTime)] = new SimpleTypeReader<DateTime>(),
|
[typeof(DateTime)] = new SimpleTypeReader<DateTime>(),
|
||||||
[typeof(DateTimeOffset)] = new SimpleTypeReader<DateTimeOffset>(),
|
[typeof(DateTimeOffset)] = new SimpleTypeReader<DateTimeOffset>(),
|
||||||
[typeof(TimeSpan)] = new SimpleTypeReader<TimeSpan>(),
|
|
||||||
[typeof(IMessage)] = new MessageTypeReader<IMessage>(),
|
[typeof(IMessage)] = new MessageTypeReader<IMessage>(),
|
||||||
[typeof(IUserMessage)] = new MessageTypeReader<IUserMessage>(),
|
[typeof(IUserMessage)] = new MessageTypeReader<IUserMessage>(),
|
||||||
[typeof(IChannel)] = new ChannelTypeReader<IChannel>(),
|
[typeof(IChannel)] = new ChannelTypeReader<IChannel>(),
|
||||||
@@ -196,16 +200,25 @@ namespace Discord.Commands
|
|||||||
//Type Readers
|
//Type Readers
|
||||||
public void AddTypeReader<T>(TypeReader reader)
|
public void AddTypeReader<T>(TypeReader reader)
|
||||||
{
|
{
|
||||||
_typeReaders[typeof(T)] = reader;
|
var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentDictionary<Type, TypeReader>());
|
||||||
|
readers[reader.GetType()] = reader;
|
||||||
}
|
}
|
||||||
public void AddTypeReader(Type type, TypeReader reader)
|
public void AddTypeReader(Type type, TypeReader reader)
|
||||||
{
|
{
|
||||||
_typeReaders[type] = reader;
|
var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary<Type, TypeReader>());
|
||||||
|
readers[reader.GetType()] = reader;
|
||||||
}
|
}
|
||||||
internal TypeReader GetTypeReader(Type type)
|
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
|
||||||
|
{
|
||||||
|
ConcurrentDictionary<Type, TypeReader> definedTypeReaders;
|
||||||
|
if (_typeReaders.TryGetValue(type, out definedTypeReaders))
|
||||||
|
return definedTypeReaders;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
internal TypeReader GetDefaultTypeReader(Type type)
|
||||||
{
|
{
|
||||||
TypeReader reader;
|
TypeReader reader;
|
||||||
if (_typeReaders.TryGetValue(type, out reader))
|
if (_defaultTypeReaders.TryGetValue(type, out reader))
|
||||||
return reader;
|
return reader;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,15 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var args = GenerateArgs(argList, paramList);
|
object[] args = GenerateArgs(argList, paramList);
|
||||||
|
|
||||||
|
foreach (var parameter in Parameters)
|
||||||
|
{
|
||||||
|
var result = await parameter.CheckPreconditionsAsync(context, args, map).ConfigureAwait(false);
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
return ExecuteResult.FromError(result);
|
||||||
|
}
|
||||||
|
|
||||||
switch (RunMode)
|
switch (RunMode)
|
||||||
{
|
{
|
||||||
case RunMode.Sync: //Always sync
|
case RunMode.Sync: //Always sync
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Discord.Commands.Builders;
|
using Discord.Commands.Builders;
|
||||||
@@ -10,6 +12,17 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
private readonly TypeReader _reader;
|
private readonly TypeReader _reader;
|
||||||
|
|
||||||
|
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 Type { get; }
|
||||||
|
public object DefaultValue { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<ParameterPreconditionAttribute> Preconditions { get; }
|
||||||
|
|
||||||
internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service)
|
internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service)
|
||||||
{
|
{
|
||||||
Command = command;
|
Command = command;
|
||||||
@@ -23,17 +36,30 @@ namespace Discord.Commands
|
|||||||
Type = builder.ParameterType;
|
Type = builder.ParameterType;
|
||||||
DefaultValue = builder.DefaultValue;
|
DefaultValue = builder.DefaultValue;
|
||||||
|
|
||||||
|
Preconditions = builder.Preconditions.ToImmutableArray();
|
||||||
|
|
||||||
_reader = builder.TypeReader;
|
_reader = builder.TypeReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandInfo Command { get; }
|
public async Task<PreconditionResult> CheckPreconditionsAsync(CommandContext context, object[] args, IDependencyMap map = null)
|
||||||
public string Name { get; }
|
{
|
||||||
public string Summary { get; }
|
if (map == null)
|
||||||
public bool IsOptional { get; }
|
map = DependencyMap.Empty;
|
||||||
public bool IsRemainder { get; }
|
|
||||||
public bool IsMultiple { get; }
|
int position = 0;
|
||||||
public Type Type { get; }
|
for(position = 0; position < Command.Parameters.Count; position++)
|
||||||
public object DefaultValue { get; }
|
if (Command.Parameters[position] == this)
|
||||||
|
break;
|
||||||
|
|
||||||
|
foreach (var precondition in Preconditions)
|
||||||
|
{
|
||||||
|
var result = await precondition.CheckPermissions(context, this, args[position], map).ConfigureAwait(false);
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PreconditionResult.FromSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<TypeReaderResult> Parse(CommandContext context, string input)
|
public async Task<TypeReaderResult> Parse(CommandContext context, string input)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user