From 41f7895620b8f0135a45ecf025d351c4f4641115 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Thu, 8 Feb 2024 13:32:48 +0100
Subject: [PATCH] Clean up reflection code
---
.../Core/Attributes/CommandAttribute.cs | 12 ++
.../Core/Attributes/GroupAttribute.cs | 12 ++
src/Commands/Core/CommandManager.cs | 4 +-
.../Core/Configuration/BuildOptions.cs | 8 --
src/Commands/Helpers/ReflectionHelpers.cs | 119 ++++++++----------
src/Commands/Reflection/IConditional.cs | 5 +
src/Commands/Reflection/Impl/CommandInfo.cs | 10 +-
src/Commands/Reflection/Impl/ModuleInfo.cs | 6 +
8 files changed, 95 insertions(+), 81 deletions(-)
diff --git a/src/Commands/Core/Attributes/CommandAttribute.cs b/src/Commands/Core/Attributes/CommandAttribute.cs
index 2eda8dd3..00c5cccf 100644
--- a/src/Commands/Core/Attributes/CommandAttribute.cs
+++ b/src/Commands/Core/Attributes/CommandAttribute.cs
@@ -1,5 +1,6 @@
using Commands.Helpers;
using System.Diagnostics.CodeAnalysis;
+using System.Text.RegularExpressions;
namespace Commands.Core
{
@@ -67,5 +68,16 @@ public CommandAttribute([DisallowNull] string name, params string[] aliases)
Name = name;
Aliases = arr;
}
+
+ internal void ValidateAliases(Regex regex)
+ {
+ foreach (var alias in Aliases)
+ {
+ if (!regex.IsMatch(alias))
+ {
+ ThrowHelpers.ThrowInvalidNaming(alias);
+ }
+ }
+ }
}
}
diff --git a/src/Commands/Core/Attributes/GroupAttribute.cs b/src/Commands/Core/Attributes/GroupAttribute.cs
index b4ddf8c3..b1a1556a 100644
--- a/src/Commands/Core/Attributes/GroupAttribute.cs
+++ b/src/Commands/Core/Attributes/GroupAttribute.cs
@@ -1,5 +1,6 @@
using Commands.Helpers;
using System.Diagnostics.CodeAnalysis;
+using System.Text.RegularExpressions;
namespace Commands.Core
{
@@ -70,5 +71,16 @@ public GroupAttribute([DisallowNull] string name, params string[] aliases)
Name = name;
Aliases = arr;
}
+
+ internal void ValidateAliases(Regex regex)
+ {
+ foreach (var alias in Aliases)
+ {
+ if (!regex.IsMatch(alias))
+ {
+ ThrowHelpers.ThrowInvalidNaming(alias);
+ }
+ }
+ }
}
}
diff --git a/src/Commands/Core/CommandManager.cs b/src/Commands/Core/CommandManager.cs
index d6e229f7..8df4a0d0 100644
--- a/src/Commands/Core/CommandManager.cs
+++ b/src/Commands/Core/CommandManager.cs
@@ -41,9 +41,7 @@ public class CommandManager(
///
/// Gets the collection containing all commands, groups and subcommands as implemented by the assemblies that were registered in the provided when creating the manager.
///
- public IReadOnlySet Commands { get; } = ReflectionHelpers.BuildComponents(converters, options)
- .SelectMany(x => x.Components)
- .ToHashSet();
+ public IReadOnlySet Commands { get; } = ReflectionHelpers.BuildComponents(converters, options).ToHashSet();
///
/// Makes an attempt at executing a command from provided .
diff --git a/src/Commands/Core/Configuration/BuildOptions.cs b/src/Commands/Core/Configuration/BuildOptions.cs
index 62a0bcb7..e4baa614 100644
--- a/src/Commands/Core/Configuration/BuildOptions.cs
+++ b/src/Commands/Core/Configuration/BuildOptions.cs
@@ -38,14 +38,6 @@ public class BuildOptions()
///
public Regex NamingRegex { get; set; } = new(@"^[a-z0-9_-]*$", RegexOptions.Compiled);
- ///
- /// Gets or sets whether commands not matching will throw during command registration.
- ///
- ///
- /// Default:
- ///
- public bool ThrowOnMatchFailure { get; set; } = true;
-
internal Dictionary KeyedConverters
{
get
diff --git a/src/Commands/Helpers/ReflectionHelpers.cs b/src/Commands/Helpers/ReflectionHelpers.cs
index ac82f1ae..206e585a 100644
--- a/src/Commands/Helpers/ReflectionHelpers.cs
+++ b/src/Commands/Helpers/ReflectionHelpers.cs
@@ -8,45 +8,56 @@ namespace Commands.Helpers
{
internal static class ReflectionHelpers
{
- public static IEnumerable BuildComponents(
- IEnumerable converters, BuildOptions options)
+ private static readonly Type m_type = typeof(ModuleBase);
+
+ public static IEnumerable BuildComponents(IEnumerable converters, BuildOptions options)
{
options.SetKeyedConverters(converters);
- var rootType = typeof(ModuleBase);
+ var components = BuildComponents(options);
+
+ foreach (var component in components)
+ {
+ if (component.IsQueryable)
+ {
+ yield return component;
+ }
+ else foreach (var subComponent in component.Components)
+ {
+ yield return subComponent;
+ }
+ }
+ }
+
+ public static IEnumerable BuildComponents(BuildOptions options)
+ {
foreach (var assembly in options.Assemblies)
{
foreach (var type in assembly.GetTypes())
{
- if (rootType.IsAssignableFrom(type)
- && !type.IsAbstract
- && !type.ContainsGenericParameters)
+ // if the type does not match module type.
+ if (!m_type.IsAssignableFrom(type) || type.IsAbstract || type.ContainsGenericParameters)
{
- var aliases = Array.Empty();
+ continue;
+ }
+
+ var aliases = Array.Empty();
- foreach (var attribute in type.GetCustomAttributes(true))
+ // search all attributes for occurrence of group, in which case we validate aliases
+ foreach (var attribute in type.GetCustomAttributes(true))
+ {
+ // if attribute is not group, we can skip it.
+ if (attribute is not GroupAttribute group)
{
- if (attribute is GroupAttribute gattribute)
- {
- foreach (var alias in gattribute.Aliases)
- {
- if (!options.NamingRegex.IsMatch(alias))
- {
- if (options.ThrowOnMatchFailure)
- {
- ThrowHelpers.ThrowInvalidNaming(alias);
- }
-
- continue;
- }
- }
-
- aliases = gattribute.Aliases;
- }
+ continue;
}
- yield return new ModuleInfo(type, null, aliases, options);
+ group.ValidateAliases(options.NamingRegex);
+
+ aliases = group.Aliases;
}
+
+ yield return new ModuleInfo(type, null, aliases, options);
}
}
}
@@ -54,27 +65,18 @@ public static IEnumerable BuildComponents(
public static IEnumerable GetModules(
ModuleInfo module, BuildOptions options)
{
- foreach (var group in module.Type.GetNestedTypes())
+ foreach (var type in module.Type.GetNestedTypes())
{
- foreach (var attribute in group.GetCustomAttributes(true))
+ foreach (var attribute in type.GetCustomAttributes(true))
{
- if (attribute is GroupAttribute gattribute)
+ if (attribute is not GroupAttribute group)
{
- foreach (var alias in gattribute.Aliases)
- {
- if (!options.NamingRegex.IsMatch(alias))
- {
- if (options.ThrowOnMatchFailure)
- {
- ThrowHelpers.ThrowInvalidNaming(alias);
- }
-
- continue;
- }
- }
-
- yield return new ModuleInfo(group, module, gattribute.Aliases, options);
+ continue;
}
+
+ group.ValidateAliases(options.NamingRegex);
+
+ yield return new ModuleInfo(type, module, group.Aliases, options);
}
}
}
@@ -83,36 +85,19 @@ public static IEnumerable GetCommands(ModuleInfo module, BuildOptio
{
foreach (var method in module.Type.GetMethods())
{
- var attributes = method.GetCustomAttributes(true);
+ var aliases = Array.Empty();
- string[] aliases = [];
- foreach (var attribute in attributes)
+ foreach (var attribute in method.GetCustomAttributes(true))
{
- if (attribute is CommandAttribute cmd)
+ if (attribute is not CommandAttribute command)
{
- foreach (var alias in cmd.Aliases)
- {
- if (!options.NamingRegex.IsMatch(alias))
- {
- if (options.ThrowOnMatchFailure)
- {
- ThrowHelpers.ThrowInvalidNaming(alias);
- }
-
- continue;
- }
- }
-
- aliases = cmd.Aliases;
+ continue;
}
- }
- if (aliases.Length == 0)
- {
- continue;
- }
+ command.ValidateAliases(options.NamingRegex);
- yield return new CommandInfo(module, method, aliases, options);
+ yield return new CommandInfo(module, method, command.Aliases, options);
+ }
}
}
diff --git a/src/Commands/Reflection/IConditional.cs b/src/Commands/Reflection/IConditional.cs
index 21ce7dfe..27dd5f1d 100644
--- a/src/Commands/Reflection/IConditional.cs
+++ b/src/Commands/Reflection/IConditional.cs
@@ -12,6 +12,11 @@ public interface IConditional : INameable
///
public string[] Aliases { get; }
+ ///
+ /// Gets if the component name is queryable.
+ ///
+ public bool IsQueryable { get; }
+
///
/// Gets an array of 's defined atop this component.
///
diff --git a/src/Commands/Reflection/Impl/CommandInfo.cs b/src/Commands/Reflection/Impl/CommandInfo.cs
index e64bb201..5ecce2f5 100644
--- a/src/Commands/Reflection/Impl/CommandInfo.cs
+++ b/src/Commands/Reflection/Impl/CommandInfo.cs
@@ -14,6 +14,12 @@ public sealed class CommandInfo : IConditional, IArgumentBucket
///
public string Name { get; }
+ ///
+ public string[] Aliases { get; }
+
+ ///
+ public bool IsQueryable { get; }
+
///
public Attribute[] Attributes { get; }
@@ -38,9 +44,6 @@ public sealed class CommandInfo : IConditional, IArgumentBucket
///
public int MaxLength { get; }
- ///
- public string[] Aliases { get; }
-
///
/// Gets the priority of this command.
///
@@ -90,6 +93,7 @@ internal CommandInfo(
Name = aliases[0];
Aliases = aliases;
+ IsQueryable = true;
MinLength = minLength;
MaxLength = maxLength;
diff --git a/src/Commands/Reflection/Impl/ModuleInfo.cs b/src/Commands/Reflection/Impl/ModuleInfo.cs
index ddc9fb5e..a0029d21 100644
--- a/src/Commands/Reflection/Impl/ModuleInfo.cs
+++ b/src/Commands/Reflection/Impl/ModuleInfo.cs
@@ -2,6 +2,7 @@
using Commands.Core;
using Commands.Helpers;
using Commands.TypeConverters;
+using System.Text.RegularExpressions;
namespace Commands.Reflection
{
@@ -16,6 +17,9 @@ public sealed class ModuleInfo : IConditional
///
public string[] Aliases { get; }
+ ///
+ public bool IsQueryable { get; }
+
///
public Attribute[] Attributes { get; }
@@ -61,10 +65,12 @@ internal ModuleInfo(
if (aliases.Length > 0)
{
+ IsQueryable = true;
Name = aliases[0];
}
else
{
+ IsQueryable = false;
Name = type.Name;
}