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; }