Update commands with C#7 features (#689)
* C#7 features in commands, CommandInfo in ModuleBase * Update TypeReaders with C#7 features and IServiceProvider * Add best-choice command selection to CommandService * Normalize type reader scores correctly * Fix logic error and rebase onto dev * Change GetMethod for SetMethod in ReflectionUtils Should be checking against setters, not getters * Ensure args/params scores do not overwhelm Priority * Remove possibility of NaNs
This commit is contained in:
committed by
RogueException
parent
41222eafeb
commit
032aba9129
@@ -13,7 +13,7 @@ namespace Discord.Commands.Builders
|
|||||||
private readonly List<string> _aliases;
|
private readonly List<string> _aliases;
|
||||||
|
|
||||||
public ModuleBuilder Module { get; }
|
public ModuleBuilder Module { get; }
|
||||||
internal Func<ICommandContext, object[], IServiceProvider, Task> Callback { get; set; }
|
internal Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> Callback { get; set; }
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Summary { get; set; }
|
public string Summary { get; set; }
|
||||||
@@ -36,7 +36,7 @@ namespace Discord.Commands.Builders
|
|||||||
_aliases = new List<string>();
|
_aliases = new List<string>();
|
||||||
}
|
}
|
||||||
//User-defined
|
//User-defined
|
||||||
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, Task> callback)
|
internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> callback)
|
||||||
: this(module)
|
: this(module)
|
||||||
{
|
{
|
||||||
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias));
|
Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias));
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ namespace Discord.Commands.Builders
|
|||||||
_preconditions.Add(precondition);
|
_preconditions.Add(precondition);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IServiceProvider, Task> callback, Action<CommandBuilder> createFunc)
|
public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> callback, Action<CommandBuilder> createFunc)
|
||||||
{
|
{
|
||||||
var builder = new CommandBuilder(this, primaryAlias, callback);
|
var builder = new CommandBuilder(this, primaryAlias, callback);
|
||||||
createFunc(builder);
|
createFunc(builder);
|
||||||
|
|||||||
@@ -12,25 +12,42 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo();
|
private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo();
|
||||||
|
|
||||||
public static IEnumerable<TypeInfo> Search(Assembly assembly)
|
public static async Task<IReadOnlyList<TypeInfo>> SearchAsync(Assembly assembly, CommandService service)
|
||||||
{
|
{
|
||||||
foreach (var type in assembly.ExportedTypes)
|
bool IsLoadableModule(TypeInfo info)
|
||||||
{
|
{
|
||||||
var typeInfo = type.GetTypeInfo();
|
return info.DeclaredMethods.Any(x => x.GetCustomAttribute<CommandAttribute>() != null) &&
|
||||||
if (IsValidModuleDefinition(typeInfo) &&
|
info.GetCustomAttribute<DontAutoLoadAttribute>() == null;
|
||||||
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute)))
|
}
|
||||||
|
|
||||||
|
List<TypeInfo> result = new List<TypeInfo>();
|
||||||
|
|
||||||
|
foreach (var typeInfo in assembly.DefinedTypes)
|
||||||
|
{
|
||||||
|
if (typeInfo.IsPublic)
|
||||||
{
|
{
|
||||||
yield return typeInfo;
|
if (IsValidModuleDefinition(typeInfo) &&
|
||||||
|
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute)))
|
||||||
|
{
|
||||||
|
result.Add(typeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsLoadableModule(typeInfo))
|
||||||
|
{
|
||||||
|
await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dictionary<Type, ModuleInfo> Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service);
|
|
||||||
public static Dictionary<Type, ModuleInfo> Build(IEnumerable<TypeInfo> validTypes, CommandService service)
|
public static Task<Dictionary<Type, ModuleInfo>> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service);
|
||||||
|
public static async Task<Dictionary<Type, ModuleInfo>> BuildAsync(IEnumerable<TypeInfo> validTypes, CommandService service)
|
||||||
{
|
{
|
||||||
/*if (!validTypes.Any())
|
/*if (!validTypes.Any())
|
||||||
throw new InvalidOperationException("Could not find any valid modules from the given selection");*/
|
throw new InvalidOperationException("Could not find any valid modules from the given selection");*/
|
||||||
|
|
||||||
var topLevelGroups = validTypes.Where(x => x.DeclaringType == null);
|
var topLevelGroups = validTypes.Where(x => x.DeclaringType == null);
|
||||||
var subGroups = validTypes.Intersect(topLevelGroups);
|
var subGroups = validTypes.Intersect(topLevelGroups);
|
||||||
|
|
||||||
@@ -48,10 +65,13 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
BuildModule(module, typeInfo, service);
|
BuildModule(module, typeInfo, service);
|
||||||
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
|
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
|
||||||
|
builtTypes.Add(typeInfo);
|
||||||
|
|
||||||
result[typeInfo.AsType()] = module.Build(service);
|
result[typeInfo.AsType()] = module.Build(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await service._cmdLogger.DebugAsync($"Successfully built and loaded {builtTypes.Count} modules.").ConfigureAwait(false);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,26 +148,32 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
foreach (var attribute in attributes)
|
foreach (var attribute in attributes)
|
||||||
{
|
{
|
||||||
// TODO: C#7 type switch
|
switch (attribute)
|
||||||
if (attribute is CommandAttribute)
|
|
||||||
{
|
{
|
||||||
var cmdAttr = attribute as CommandAttribute;
|
case CommandAttribute command:
|
||||||
builder.AddAliases(cmdAttr.Text);
|
builder.AddAliases(command.Text);
|
||||||
builder.RunMode = cmdAttr.RunMode;
|
builder.RunMode = command.RunMode;
|
||||||
builder.Name = builder.Name ?? cmdAttr.Text;
|
builder.Name = builder.Name ?? command.Text;
|
||||||
|
break;
|
||||||
|
case NameAttribute name:
|
||||||
|
builder.Name = name.Text;
|
||||||
|
break;
|
||||||
|
case PriorityAttribute priority:
|
||||||
|
builder.Priority = priority.Priority;
|
||||||
|
break;
|
||||||
|
case SummaryAttribute summary:
|
||||||
|
builder.Summary = summary.Text;
|
||||||
|
break;
|
||||||
|
case RemarksAttribute remarks:
|
||||||
|
builder.Remarks = remarks.Text;
|
||||||
|
break;
|
||||||
|
case AliasAttribute alias:
|
||||||
|
builder.AddAliases(alias.Aliases);
|
||||||
|
break;
|
||||||
|
case PreconditionAttribute precondition:
|
||||||
|
builder.AddPrecondition(precondition);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (attribute is NameAttribute)
|
|
||||||
builder.Name = (attribute as NameAttribute).Text;
|
|
||||||
else if (attribute is PriorityAttribute)
|
|
||||||
builder.Priority = (attribute as PriorityAttribute).Priority;
|
|
||||||
else if (attribute is SummaryAttribute)
|
|
||||||
builder.Summary = (attribute as SummaryAttribute).Text;
|
|
||||||
else if (attribute is RemarksAttribute)
|
|
||||||
builder.Remarks = (attribute as RemarksAttribute).Text;
|
|
||||||
else if (attribute is AliasAttribute)
|
|
||||||
builder.AddAliases((attribute as AliasAttribute).Aliases);
|
|
||||||
else if (attribute is PreconditionAttribute)
|
|
||||||
builder.AddPrecondition(attribute as PreconditionAttribute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (builder.Name == null)
|
if (builder.Name == null)
|
||||||
@@ -165,19 +191,19 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
var createInstance = ReflectionUtils.CreateBuilder<IModuleBase>(typeInfo, service);
|
var createInstance = ReflectionUtils.CreateBuilder<IModuleBase>(typeInfo, service);
|
||||||
|
|
||||||
builder.Callback = async (ctx, args, map) =>
|
builder.Callback = async (ctx, args, map, cmd) =>
|
||||||
{
|
{
|
||||||
var instance = createInstance(map);
|
var instance = createInstance(map);
|
||||||
instance.SetContext(ctx);
|
instance.SetContext(ctx);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
instance.BeforeExecute();
|
instance.BeforeExecute(cmd);
|
||||||
var task = method.Invoke(instance, args) as Task ?? Task.Delay(0);
|
var task = method.Invoke(instance, args) as Task ?? Task.Delay(0);
|
||||||
await task.ConfigureAwait(false);
|
await task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
instance.AfterExecute();
|
instance.AfterExecute(cmd);
|
||||||
(instance as IDisposable)?.Dispose();
|
(instance as IDisposable)?.Dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -195,24 +221,24 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
foreach (var attribute in attributes)
|
foreach (var attribute in attributes)
|
||||||
{
|
{
|
||||||
// TODO: C#7 type switch
|
switch (attribute)
|
||||||
if (attribute is SummaryAttribute)
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
builder.IsMultiple = true;
|
case SummaryAttribute summary:
|
||||||
paramType = paramType.GetElementType();
|
builder.Summary = summary.Text;
|
||||||
}
|
break;
|
||||||
else if (attribute is RemainderAttribute)
|
case OverrideTypeReaderAttribute typeReader:
|
||||||
{
|
builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader);
|
||||||
if (position != count-1)
|
break;
|
||||||
throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}");
|
case ParamArrayAttribute _:
|
||||||
|
builder.IsMultiple = true;
|
||||||
builder.IsRemainder = true;
|
paramType = paramType.GetElementType();
|
||||||
|
break;
|
||||||
|
case RemainderAttribute _:
|
||||||
|
if (position != count - 1)
|
||||||
|
throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}");
|
||||||
|
|
||||||
|
builder.IsRemainder = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
|
public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
|
||||||
=> Command.CheckPreconditionsAsync(context, services);
|
=> Command.CheckPreconditionsAsync(context, services);
|
||||||
public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null)
|
public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null)
|
||||||
=> Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult);
|
=> Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult, services);
|
||||||
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
|
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
|
||||||
=> Command.ExecuteAsync(context, argList, paramList, services);
|
=> Command.ExecuteAsync(context, argList, paramList, services);
|
||||||
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
|
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Immutable;
|
using System;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ namespace Discord.Commands
|
|||||||
QuotedParameter
|
QuotedParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<ParseResult> ParseArgs(CommandInfo command, ICommandContext context, string input, int startPos)
|
public static async Task<ParseResult> ParseArgs(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos)
|
||||||
{
|
{
|
||||||
ParameterInfo curParam = null;
|
ParameterInfo curParam = null;
|
||||||
StringBuilder argBuilder = new StringBuilder(input.Length);
|
StringBuilder argBuilder = new StringBuilder(input.Length);
|
||||||
@@ -110,7 +111,7 @@ namespace Discord.Commands
|
|||||||
if (curParam == null)
|
if (curParam == null)
|
||||||
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters.");
|
return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters.");
|
||||||
|
|
||||||
var typeReaderResult = await curParam.Parse(context, argString).ConfigureAwait(false);
|
var typeReaderResult = await curParam.Parse(context, argString, services).ConfigureAwait(false);
|
||||||
if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches)
|
if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches)
|
||||||
return ParseResult.FromError(typeReaderResult);
|
return ParseResult.FromError(typeReaderResult);
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
if (curParam != null && curParam.IsRemainder)
|
if (curParam != null && curParam.IsRemainder)
|
||||||
{
|
{
|
||||||
var typeReaderResult = await curParam.Parse(context, argBuilder.ToString()).ConfigureAwait(false);
|
var typeReaderResult = await curParam.Parse(context, argBuilder.ToString(), services).ConfigureAwait(false);
|
||||||
if (!typeReaderResult.IsSuccess)
|
if (!typeReaderResult.IsSuccess)
|
||||||
return ParseResult.FromError(typeReaderResult);
|
return ParseResult.FromError(typeReaderResult);
|
||||||
argList.Add(typeReaderResult);
|
argList.Add(typeReaderResult);
|
||||||
|
|||||||
@@ -33,7 +33,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 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)
|
||||||
@@ -59,6 +59,9 @@ namespace Discord.Commands
|
|||||||
foreach (var type in PrimitiveParsers.SupportedTypes)
|
foreach (var type in PrimitiveParsers.SupportedTypes)
|
||||||
_defaultTypeReaders[type] = PrimitiveTypeReader.Create(type);
|
_defaultTypeReaders[type] = PrimitiveTypeReader.Create(type);
|
||||||
|
|
||||||
|
_defaultTypeReaders[typeof(string)] =
|
||||||
|
new PrimitiveTypeReader<string>((string x, out string y) => { y = x; return true; }, 0);
|
||||||
|
|
||||||
var entityTypeReaders = ImmutableList.CreateBuilder<Tuple<Type, Type>>();
|
var entityTypeReaders = ImmutableList.CreateBuilder<Tuple<Type, Type>>();
|
||||||
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IMessage), typeof(MessageTypeReader<>)));
|
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IMessage), typeof(MessageTypeReader<>)));
|
||||||
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IChannel), typeof(ChannelTypeReader<>)));
|
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IChannel), typeof(ChannelTypeReader<>)));
|
||||||
@@ -95,7 +98,7 @@ namespace Discord.Commands
|
|||||||
if (_typedModuleDefs.ContainsKey(type))
|
if (_typedModuleDefs.ContainsKey(type))
|
||||||
throw new ArgumentException($"This module has already been added.");
|
throw new ArgumentException($"This module has already been added.");
|
||||||
|
|
||||||
var module = ModuleClassBuilder.Build(this, typeInfo).FirstOrDefault();
|
var module = (await ModuleClassBuilder.BuildAsync(this, typeInfo).ConfigureAwait(false)).FirstOrDefault();
|
||||||
|
|
||||||
if (module.Value == default(ModuleInfo))
|
if (module.Value == default(ModuleInfo))
|
||||||
throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?");
|
throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?");
|
||||||
@@ -114,8 +117,8 @@ namespace Discord.Commands
|
|||||||
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var types = ModuleClassBuilder.Search(assembly).ToArray();
|
var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false);
|
||||||
var moduleDefs = ModuleClassBuilder.Build(types, this);
|
var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var info in moduleDefs)
|
foreach (var info in moduleDefs)
|
||||||
{
|
{
|
||||||
@@ -161,8 +164,7 @@ namespace Discord.Commands
|
|||||||
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ModuleInfo module;
|
if (!_typedModuleDefs.TryRemove(type, out var module))
|
||||||
if (!_typedModuleDefs.TryRemove(type, out module))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return RemoveModuleInternal(module);
|
return RemoveModuleInternal(module);
|
||||||
@@ -196,20 +198,18 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
public void AddTypeReader(Type type, TypeReader reader)
|
public void AddTypeReader(Type type, TypeReader reader)
|
||||||
{
|
{
|
||||||
var readers = _typeReaders.GetOrAdd(type, x=> new ConcurrentDictionary<Type, TypeReader>());
|
var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary<Type, TypeReader>());
|
||||||
readers[reader.GetType()] = reader;
|
readers[reader.GetType()] = reader;
|
||||||
}
|
}
|
||||||
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
|
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<Type, TypeReader> definedTypeReaders;
|
if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
|
||||||
if (_typeReaders.TryGetValue(type, out definedTypeReaders))
|
|
||||||
return definedTypeReaders;
|
return definedTypeReaders;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
internal TypeReader GetDefaultTypeReader(Type type)
|
internal TypeReader GetDefaultTypeReader(Type type)
|
||||||
{
|
{
|
||||||
TypeReader reader;
|
if (_defaultTypeReaders.TryGetValue(type, out var reader))
|
||||||
if (_defaultTypeReaders.TryGetValue(type, out reader))
|
|
||||||
return reader;
|
return reader;
|
||||||
var typeInfo = type.GetTypeInfo();
|
var typeInfo = type.GetTypeInfo();
|
||||||
|
|
||||||
@@ -235,13 +235,13 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Execution
|
//Execution
|
||||||
public SearchResult Search(ICommandContext context, int argPos)
|
public SearchResult Search(ICommandContext context, int argPos)
|
||||||
=> Search(context, context.Message.Content.Substring(argPos));
|
=> Search(context, context.Message.Content.Substring(argPos));
|
||||||
public SearchResult Search(ICommandContext context, string input)
|
public SearchResult Search(ICommandContext context, string input)
|
||||||
{
|
{
|
||||||
string searchInput = _caseSensitive ? input : input.ToLowerInvariant();
|
string searchInput = _caseSensitive ? input : input.ToLowerInvariant();
|
||||||
var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray();
|
var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray();
|
||||||
|
|
||||||
if (matches.Length > 0)
|
if (matches.Length > 0)
|
||||||
return SearchResult.FromSuccess(input, matches);
|
return SearchResult.FromSuccess(input, matches);
|
||||||
else
|
else
|
||||||
@@ -259,46 +259,83 @@ namespace Discord.Commands
|
|||||||
return searchResult;
|
return searchResult;
|
||||||
|
|
||||||
var commands = searchResult.Commands;
|
var commands = searchResult.Commands;
|
||||||
for (int i = 0; i < commands.Count; i++)
|
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
|
||||||
|
|
||||||
|
foreach (var match in commands)
|
||||||
{
|
{
|
||||||
var preconditionResult = await commands[i].CheckPreconditionsAsync(context, services).ConfigureAwait(false);
|
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false);
|
||||||
if (!preconditionResult.IsSuccess)
|
|
||||||
{
|
|
||||||
if (commands.Count == 1)
|
|
||||||
return preconditionResult;
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false);
|
|
||||||
if (!parseResult.IsSuccess)
|
|
||||||
{
|
|
||||||
if (parseResult.Error == CommandError.MultipleMatches)
|
|
||||||
{
|
|
||||||
IReadOnlyList<TypeReaderValue> argList, paramList;
|
|
||||||
switch (multiMatchHandling)
|
|
||||||
{
|
|
||||||
case MultiMatchHandling.Best:
|
|
||||||
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
|
|
||||||
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
|
|
||||||
parseResult = ParseResult.FromSuccess(argList, paramList);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parseResult.IsSuccess)
|
|
||||||
{
|
|
||||||
if (commands.Count == 1)
|
|
||||||
return parseResult;
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await commands[i].ExecuteAsync(context, parseResult, services).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload.");
|
var successfulPreconditions = preconditionResults
|
||||||
|
.Where(x => x.Value.IsSuccess)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (successfulPreconditions.Length == 0)
|
||||||
|
{
|
||||||
|
//All preconditions failed, return the one from the highest priority command
|
||||||
|
var bestCandidate = preconditionResults
|
||||||
|
.OrderByDescending(x => x.Key.Command.Priority)
|
||||||
|
.FirstOrDefault(x => !x.Value.IsSuccess);
|
||||||
|
return bestCandidate.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we get this far, at least one precondition was successful.
|
||||||
|
|
||||||
|
var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
|
||||||
|
foreach (var pair in successfulPreconditions)
|
||||||
|
{
|
||||||
|
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (parseResult.Error == CommandError.MultipleMatches)
|
||||||
|
{
|
||||||
|
IReadOnlyList<TypeReaderValue> argList, paramList;
|
||||||
|
switch (multiMatchHandling)
|
||||||
|
{
|
||||||
|
case MultiMatchHandling.Best:
|
||||||
|
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
|
||||||
|
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
|
||||||
|
parseResult = ParseResult.FromSuccess(argList, paramList);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseResultsDict[pair.Key] = parseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the 'score' of a command given a parse result
|
||||||
|
float CalculateScore(CommandMatch match, ParseResult parseResult)
|
||||||
|
{
|
||||||
|
float argValuesScore = 0, paramValuesScore = 0;
|
||||||
|
|
||||||
|
if (match.Command.Parameters.Count > 0)
|
||||||
|
{
|
||||||
|
argValuesScore = parseResult.ArgValues.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) / match.Command.Parameters.Count;
|
||||||
|
paramValuesScore = parseResult.ParamValues.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) / match.Command.Parameters.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
|
||||||
|
return match.Command.Priority + totalArgsScore * 0.99f;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Order the parse results by their score so that we choose the most likely result to execute
|
||||||
|
var parseResults = parseResultsDict
|
||||||
|
.OrderByDescending(x => CalculateScore(x.Key, x.Value));
|
||||||
|
|
||||||
|
var successfulParses = parseResults
|
||||||
|
.Where(x => x.Value.IsSuccess)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (successfulParses.Length == 0)
|
||||||
|
{
|
||||||
|
//All parses failed, return the one from the highest priority command, using score as a tie breaker
|
||||||
|
var bestMatch = parseResults
|
||||||
|
.FirstOrDefault(x => !x.Value.IsSuccess);
|
||||||
|
return bestMatch.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we get this far, at least one parse was successful. Execute the most likely overload.
|
||||||
|
var chosenOverload = successfulParses[0];
|
||||||
|
return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
{
|
{
|
||||||
void SetContext(ICommandContext context);
|
void SetContext(ICommandContext context);
|
||||||
|
|
||||||
void BeforeExecute();
|
void BeforeExecute(CommandInfo command);
|
||||||
|
|
||||||
void AfterExecute();
|
void AfterExecute(CommandInfo command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace Discord.Commands
|
|||||||
private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
|
private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
|
||||||
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();
|
private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();
|
||||||
|
|
||||||
private readonly Func<ICommandContext, object[], IServiceProvider, Task> _action;
|
private readonly Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> _action;
|
||||||
|
|
||||||
public ModuleInfo Module { get; }
|
public ModuleInfo Module { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
@@ -105,15 +105,17 @@ namespace Discord.Commands
|
|||||||
return PreconditionResult.FromSuccess();
|
return PreconditionResult.FromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null)
|
public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null)
|
||||||
{
|
{
|
||||||
|
services = services ?? EmptyServiceProvider.Instance;
|
||||||
|
|
||||||
if (!searchResult.IsSuccess)
|
if (!searchResult.IsSuccess)
|
||||||
return ParseResult.FromError(searchResult);
|
return ParseResult.FromError(searchResult);
|
||||||
if (preconditionResult != null && !preconditionResult.IsSuccess)
|
if (preconditionResult != null && !preconditionResult.IsSuccess)
|
||||||
return ParseResult.FromError(preconditionResult);
|
return ParseResult.FromError(preconditionResult);
|
||||||
|
|
||||||
string input = searchResult.Text.Substring(startIndex);
|
string input = searchResult.Text.Substring(startIndex);
|
||||||
return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false);
|
return await CommandParser.ParseArgs(this, context, services, input, 0).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
|
public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
|
||||||
@@ -181,7 +183,7 @@ namespace Discord.Commands
|
|||||||
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
|
await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _action(context, args, services).ConfigureAwait(false);
|
await _action(context, args, services, this).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,9 +54,10 @@ namespace Discord.Commands
|
|||||||
return PreconditionResult.FromSuccess();
|
return PreconditionResult.FromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TypeReaderResult> Parse(ICommandContext context, string input)
|
public async Task<TypeReaderResult> Parse(ICommandContext context, string input, IServiceProvider services = null)
|
||||||
{
|
{
|
||||||
return await _reader.Read(context, input).ConfigureAwait(false);
|
services = services ?? EmptyServiceProvider.Instance;
|
||||||
|
return await _reader.Read(context, input, services).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ namespace Discord.Commands
|
|||||||
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false);
|
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void BeforeExecute()
|
protected virtual void BeforeExecute(CommandInfo command)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void AfterExecute()
|
protected virtual void AfterExecute(CommandInfo command)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,13 +27,11 @@ namespace Discord.Commands
|
|||||||
void IModuleBase.SetContext(ICommandContext context)
|
void IModuleBase.SetContext(ICommandContext context)
|
||||||
{
|
{
|
||||||
var newValue = context as T;
|
var newValue = context as T;
|
||||||
if (newValue == null)
|
Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}");
|
||||||
throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}");
|
|
||||||
Context = newValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IModuleBase.BeforeExecute() => BeforeExecute();
|
void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command);
|
||||||
|
|
||||||
void IModuleBase.AfterExecute() => AfterExecute();
|
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,6 @@ namespace Discord.Commands
|
|||||||
parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate<DateTimeOffset>)DateTimeOffset.TryParse;
|
parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate<DateTimeOffset>)DateTimeOffset.TryParse;
|
||||||
parserBuilder[typeof(TimeSpan)] = (TryParseDelegate<TimeSpan>)TimeSpan.TryParse;
|
parserBuilder[typeof(TimeSpan)] = (TryParseDelegate<TimeSpan>)TimeSpan.TryParse;
|
||||||
parserBuilder[typeof(char)] = (TryParseDelegate<char>)char.TryParse;
|
parserBuilder[typeof(char)] = (TryParseDelegate<char>)char.TryParse;
|
||||||
parserBuilder[typeof(string)] = (TryParseDelegate<string>)delegate (string str, out string value)
|
|
||||||
{
|
|
||||||
value = str;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
return parserBuilder.ToImmutable();
|
return parserBuilder.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Discord.Commands
|
|||||||
internal class ChannelTypeReader<T> : TypeReader
|
internal class ChannelTypeReader<T> : TypeReader
|
||||||
where T : class, IChannel
|
where T : class, IChannel
|
||||||
{
|
{
|
||||||
public override async Task<TypeReaderResult> Read(ICommandContext context, string input)
|
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||||
{
|
{
|
||||||
if (context.Guild != null)
|
if (context.Guild != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,12 +44,11 @@ namespace Discord.Commands
|
|||||||
_enumsByValue = byValueBuilder.ToImmutable();
|
_enumsByValue = byValueBuilder.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
|
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||||
{
|
{
|
||||||
T baseValue;
|
|
||||||
object enumValue;
|
object enumValue;
|
||||||
|
|
||||||
if (_tryParse(input, out baseValue))
|
if (_tryParse(input, out T baseValue))
|
||||||
{
|
{
|
||||||
if (_enumsByValue.TryGetValue(baseValue, out enumValue))
|
if (_enumsByValue.TryGetValue(baseValue, out enumValue))
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(enumValue));
|
return Task.FromResult(TypeReaderResult.FromSuccess(enumValue));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Globalization;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
@@ -6,15 +7,14 @@ namespace Discord.Commands
|
|||||||
internal class MessageTypeReader<T> : TypeReader
|
internal class MessageTypeReader<T> : TypeReader
|
||||||
where T : class, IMessage
|
where T : class, IMessage
|
||||||
{
|
{
|
||||||
public override async Task<TypeReaderResult> Read(ICommandContext context, string input)
|
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||||
{
|
{
|
||||||
ulong id;
|
ulong id;
|
||||||
|
|
||||||
//By Id (1.0)
|
//By Id (1.0)
|
||||||
if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id))
|
if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id))
|
||||||
{
|
{
|
||||||
var msg = await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T;
|
if (await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg)
|
||||||
if (msg != null)
|
|
||||||
return TypeReaderResult.FromSuccess(msg);
|
return TypeReaderResult.FromSuccess(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,17 +15,25 @@ namespace Discord.Commands
|
|||||||
internal class PrimitiveTypeReader<T> : TypeReader
|
internal class PrimitiveTypeReader<T> : TypeReader
|
||||||
{
|
{
|
||||||
private readonly TryParseDelegate<T> _tryParse;
|
private readonly TryParseDelegate<T> _tryParse;
|
||||||
|
private readonly float _score;
|
||||||
|
|
||||||
public PrimitiveTypeReader()
|
public PrimitiveTypeReader()
|
||||||
|
: this(PrimitiveParsers.Get<T>(), 1)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public PrimitiveTypeReader(TryParseDelegate<T> tryParse, float score)
|
||||||
{
|
{
|
||||||
_tryParse = PrimitiveParsers.Get<T>();
|
if (score < 0 || score > 1)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(score), score, "Scores must be within the range [0, 1]");
|
||||||
|
|
||||||
|
_tryParse = tryParse;
|
||||||
|
_score = score;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
|
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||||
{
|
{
|
||||||
T value;
|
if (_tryParse(input, out T value))
|
||||||
if (_tryParse(input, out value))
|
return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score)));
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(value));
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}"));
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Discord.Commands
|
|||||||
internal class RoleTypeReader<T> : TypeReader
|
internal class RoleTypeReader<T> : TypeReader
|
||||||
where T : class, IRole
|
where T : class, IRole
|
||||||
{
|
{
|
||||||
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
|
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||||
{
|
{
|
||||||
ulong id;
|
ulong id;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
public abstract class TypeReader
|
public abstract class TypeReader
|
||||||
{
|
{
|
||||||
public abstract Task<TypeReaderResult> Read(ICommandContext context, string input);
|
public abstract Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Discord.Commands
|
|||||||
internal class UserTypeReader<T> : TypeReader
|
internal class UserTypeReader<T> : TypeReader
|
||||||
where T : class, IUser
|
where T : class, IUser
|
||||||
{
|
{
|
||||||
public override async Task<TypeReaderResult> Read(ICommandContext context, string input)
|
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
|
||||||
{
|
{
|
||||||
var results = new Dictionary<ulong, TypeReaderValue>();
|
var results = new Dictionary<ulong, TypeReaderValue>();
|
||||||
IReadOnlyCollection<IUser> channelUsers = (await context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way?
|
IReadOnlyCollection<IUser> channelUsers = (await context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way?
|
||||||
@@ -43,8 +43,7 @@ namespace Discord.Commands
|
|||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
{
|
{
|
||||||
string username = input.Substring(0, index);
|
string username = input.Substring(0, index);
|
||||||
ushort discriminator;
|
if (ushort.TryParse(input.Substring(index + 1), out ushort discriminator))
|
||||||
if (ushort.TryParse(input.Substring(index + 1), out discriminator))
|
|
||||||
{
|
{
|
||||||
var channelUser = channelUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator &&
|
var channelUser = channelUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator &&
|
||||||
string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase));
|
string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
foreach (var prop in ownerType.DeclaredProperties)
|
foreach (var prop in ownerType.DeclaredProperties)
|
||||||
{
|
{
|
||||||
if (prop.GetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute<DontInjectAttribute>() == null)
|
if (prop.SetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute<DontInjectAttribute>() == null)
|
||||||
result.Add(prop);
|
result.Add(prop);
|
||||||
}
|
}
|
||||||
ownerType = ownerType.BaseType.GetTypeInfo();
|
ownerType = ownerType.BaseType.GetTypeInfo();
|
||||||
|
|||||||
Reference in New Issue
Block a user