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:
Finite Reality
2017-06-29 21:43:55 +01:00
committed by RogueException
parent 41222eafeb
commit 032aba9129
19 changed files with 213 additions and 146 deletions

View File

@@ -12,25 +12,42 @@ namespace Discord.Commands
{
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();
if (IsValidModuleDefinition(typeInfo) &&
!typeInfo.IsDefined(typeof(DontAutoLoadAttribute)))
return info.DeclaredMethods.Any(x => x.GetCustomAttribute<CommandAttribute>() != null) &&
info.GetCustomAttribute<DontAutoLoadAttribute>() == null;
}
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())
throw new InvalidOperationException("Could not find any valid modules from the given selection");*/
var topLevelGroups = validTypes.Where(x => x.DeclaringType == null);
var subGroups = validTypes.Intersect(topLevelGroups);
@@ -48,10 +65,13 @@ namespace Discord.Commands
BuildModule(module, typeInfo, service);
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
builtTypes.Add(typeInfo);
result[typeInfo.AsType()] = module.Build(service);
}
await service._cmdLogger.DebugAsync($"Successfully built and loaded {builtTypes.Count} modules.").ConfigureAwait(false);
return result;
}
@@ -128,26 +148,32 @@ namespace Discord.Commands
foreach (var attribute in attributes)
{
// TODO: C#7 type switch
if (attribute is CommandAttribute)
switch (attribute)
{
var cmdAttr = attribute as CommandAttribute;
builder.AddAliases(cmdAttr.Text);
builder.RunMode = cmdAttr.RunMode;
builder.Name = builder.Name ?? cmdAttr.Text;
case CommandAttribute command:
builder.AddAliases(command.Text);
builder.RunMode = command.RunMode;
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)
@@ -165,19 +191,19 @@ namespace Discord.Commands
var createInstance = ReflectionUtils.CreateBuilder<IModuleBase>(typeInfo, service);
builder.Callback = async (ctx, args, map) =>
builder.Callback = async (ctx, args, map, cmd) =>
{
var instance = createInstance(map);
instance.SetContext(ctx);
try
{
instance.BeforeExecute();
instance.BeforeExecute(cmd);
var task = method.Invoke(instance, args) as Task ?? Task.Delay(0);
await task.ConfigureAwait(false);
}
finally
{
instance.AfterExecute();
instance.AfterExecute(cmd);
(instance as IDisposable)?.Dispose();
}
};
@@ -195,24 +221,24 @@ namespace Discord.Commands
foreach (var attribute in attributes)
{
// TODO: C#7 type switch
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)
switch (attribute)
{
builder.IsMultiple = true;
paramType = paramType.GetElementType();
}
else if (attribute is 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;
case SummaryAttribute summary:
builder.Summary = summary.Text;
break;
case OverrideTypeReaderAttribute typeReader:
builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader);
break;
case ParamArrayAttribute _:
builder.IsMultiple = 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;
}
}