Skip to content

Commit

Permalink
Clean up reflection code
Browse files Browse the repository at this point in the history
  • Loading branch information
csmir committed Feb 8, 2024
1 parent 8612fed commit 41f7895
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 81 deletions.
12 changes: 12 additions & 0 deletions src/Commands/Core/Attributes/CommandAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Commands.Helpers;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;

namespace Commands.Core
{
Expand Down Expand Up @@ -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);
}
}
}
}
}
12 changes: 12 additions & 0 deletions src/Commands/Core/Attributes/GroupAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Commands.Helpers;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;

namespace Commands.Core
{
Expand Down Expand Up @@ -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);
}
}
}
}
}
4 changes: 1 addition & 3 deletions src/Commands/Core/CommandManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ public class CommandManager(
/// <summary>
/// Gets the collection containing all commands, groups and subcommands as implemented by the assemblies that were registered in the <see cref="BuildOptions"/> provided when creating the manager.
/// </summary>
public IReadOnlySet<IConditional> Commands { get; } = ReflectionHelpers.BuildComponents(converters, options)
.SelectMany(x => x.Components)
.ToHashSet();
public IReadOnlySet<IConditional> Commands { get; } = ReflectionHelpers.BuildComponents(converters, options).ToHashSet();

/// <summary>
/// Makes an attempt at executing a command from provided <paramref name="args"/>.
Expand Down
8 changes: 0 additions & 8 deletions src/Commands/Core/Configuration/BuildOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,6 @@ public class BuildOptions()
/// </remarks>
public Regex NamingRegex { get; set; } = new(@"^[a-z0-9_-]*$", RegexOptions.Compiled);

/// <summary>
/// Gets or sets whether commands not matching <see cref="NamingRegex"/> will throw during command registration.
/// </summary>
/// <remarks>
/// Default: <see langword="true"/>
/// </remarks>
public bool ThrowOnMatchFailure { get; set; } = true;

internal Dictionary<Type, TypeConverterBase> KeyedConverters
{
get
Expand Down
119 changes: 52 additions & 67 deletions src/Commands/Helpers/ReflectionHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,73 +8,75 @@ namespace Commands.Helpers
{
internal static class ReflectionHelpers
{
public static IEnumerable<ModuleInfo> BuildComponents(
IEnumerable<TypeConverterBase> converters, BuildOptions options)
private static readonly Type m_type = typeof(ModuleBase);

public static IEnumerable<IConditional> BuildComponents(IEnumerable<TypeConverterBase> 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<ModuleInfo> 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<string>();
continue;
}

var aliases = Array.Empty<string>();

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

public static IEnumerable<ModuleInfo> 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);
}
}
}
Expand All @@ -83,36 +85,19 @@ public static IEnumerable<CommandInfo> GetCommands(ModuleInfo module, BuildOptio
{
foreach (var method in module.Type.GetMethods())
{
var attributes = method.GetCustomAttributes(true);
var aliases = Array.Empty<string>();

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

Expand Down
5 changes: 5 additions & 0 deletions src/Commands/Reflection/IConditional.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ public interface IConditional : INameable
/// </summary>
public string[] Aliases { get; }

/// <summary>
/// Gets if the component name is queryable.
/// </summary>
public bool IsQueryable { get; }

/// <summary>
/// Gets an array of <see cref="PreconditionAttribute"/>'s defined atop this component.
/// </summary>
Expand Down
10 changes: 7 additions & 3 deletions src/Commands/Reflection/Impl/CommandInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ public sealed class CommandInfo : IConditional, IArgumentBucket
/// <inheritdoc />
public string Name { get; }

/// <inheritdoc />
public string[] Aliases { get; }

/// <inheritdoc />
public bool IsQueryable { get; }

/// <inheritdoc />
public Attribute[] Attributes { get; }

Expand All @@ -38,9 +44,6 @@ public sealed class CommandInfo : IConditional, IArgumentBucket
/// <inheritdoc />
public int MaxLength { get; }

/// <inheritdoc />
public string[] Aliases { get; }

/// <summary>
/// Gets the priority of this command.
/// </summary>
Expand Down Expand Up @@ -90,6 +93,7 @@ internal CommandInfo(

Name = aliases[0];
Aliases = aliases;
IsQueryable = true;

MinLength = minLength;
MaxLength = maxLength;
Expand Down
6 changes: 6 additions & 0 deletions src/Commands/Reflection/Impl/ModuleInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Commands.Core;
using Commands.Helpers;
using Commands.TypeConverters;
using System.Text.RegularExpressions;

namespace Commands.Reflection
{
Expand All @@ -16,6 +17,9 @@ public sealed class ModuleInfo : IConditional
/// <inheritdoc />
public string[] Aliases { get; }

/// <inheritdoc />
public bool IsQueryable { get; }

/// <inheritdoc />
public Attribute[] Attributes { get; }

Expand Down Expand Up @@ -61,10 +65,12 @@ internal ModuleInfo(

if (aliases.Length > 0)
{
IsQueryable = true;
Name = aliases[0];
}
else
{
IsQueryable = false;
Name = type.Name;
}

Expand Down

0 comments on commit 41f7895

Please sign in to comment.