Pointless Waymarks Tools

Artifact [a9d27fc26e]
Login

Artifact [a9d27fc26e]

Artifact a9d27fc26e4e8f24628f9fff699cd50bc90a666a95d43c6c8b7382149e405cde:


using System.Text.RegularExpressions;
using CommunityToolkit.Mvvm.Input;
using Metalama.Framework.Aspects;
using Metalama.Framework.Code;

namespace PointlessWaymarks.LlamaAspects;

public class GenerateStatusCommandsAttribute : TypeAspect
{
    public override void BuildAspect(IAspectBuilder<INamedType> builder)
    {
        foreach (var method in builder.Target.Methods.Where(p =>
                     (p.Attributes.Any(typeof(BlockingCommandAttribute)) ||
                      p.Attributes.Any(typeof(NonBlockingCommandAttribute))) && p.Parameters.Count == 0))
            builder.Advice.IntroduceAutomaticProperty(method.DeclaringType, $"{method.Name}Command",
                TypeFactory.GetType(typeof(RelayCommand)).ToNullableType(), IntroductionScope.Default,
                OverrideStrategy.Ignore,
                propertyBuilder => propertyBuilder.Accessibility = Accessibility.Public);

        foreach (var method in builder.Target.Methods.Where(p =>
                     (p.Attributes.Any(typeof(BlockingCommandAttribute)) ||
                      p.Attributes.Any(typeof(NonBlockingCommandAttribute))) && p.Parameters.Count == 1 &&
                     p.Parameters[0].Type.ToType() != typeof(CancellationToken)))
        {
            var firstParameterType = method.Parameters[0].Type;

            builder.Advice.IntroduceAutomaticProperty(method.DeclaringType, $"{method.Name}Command",
                ((INamedType)TypeFactory.GetType(typeof(RelayCommand<>))).WithTypeArguments(firstParameterType)
                .ToNullableType(),
                IntroductionScope.Default,
                OverrideStrategy.Ignore,
                propertyBuilder =>
                {
                    propertyBuilder.Accessibility = Accessibility.Public;
                    propertyBuilder.InitializerExpression = null;
                });
        }

        foreach (var method in builder.Target.Methods.Where(p =>
                     (p.Attributes.Any(typeof(BlockingCommandAttribute)) ||
                      p.Attributes.Any(typeof(NonBlockingCommandAttribute))) && p.Parameters.Count == 1 &&
                     p.Parameters[0].Type.ToType() == typeof(CancellationToken)))
            builder.Advice.IntroduceAutomaticProperty(method.DeclaringType, $"{method.Name}Command",
                TypeFactory.GetType(typeof(RelayCommand)).ToNullableType(), IntroductionScope.Default,
                OverrideStrategy.Ignore,
                propertyBuilder => propertyBuilder.Accessibility = Accessibility.Public);

        builder.Advice.IntroduceMethod(builder.Target, "BuildCommands");
    }

    [Template]
    public void BuildCommands()
    {
        foreach (var loopMethods in meta.Target.Type.Methods.Where(p =>
                     p.Attributes.Any(typeof(BlockingCommandAttribute)) && p.Parameters.Count == 0))
            meta.InsertStatement(
                $"{loopMethods.Name}Command = StatusContext.RunBlockingTaskCommand({loopMethods.Name});");

        foreach (var loopMethods in meta.Target.Type.Methods.Where(p =>
                     p.Attributes.Any(typeof(NonBlockingCommandAttribute)) && p.Parameters.Count == 0))
            meta.InsertStatement(
                $"{loopMethods.Name}Command = StatusContext.RunNonBlockingTaskCommand({loopMethods.Name});");

        foreach (var loopMethods in meta.Target.Type.Methods.Where(p =>
                     p.Attributes.Any(typeof(BlockingCommandAttribute)) && p.Parameters.Count == 1))
            if (loopMethods.Parameters[0].Type != TypeFactory.GetType(typeof(CancellationToken)))
                meta.InsertStatement(
                    $"{loopMethods.Name}Command = StatusContext.RunBlockingTaskCommand<{loopMethods.Parameters[0].Type}>({loopMethods.Name});");
            else
                meta.InsertStatement(
                    $"{loopMethods.Name}Command = StatusContext.RunBlockingTaskWithCancellationCommand({loopMethods.Name}, \"Cancel {SplitCamelCase(loopMethods.Name)}\");");

        foreach (var loopMethods in meta.Target.Type.Methods.Where(p =>
                     p.Attributes.Any(typeof(NonBlockingCommandAttribute)) && p.Parameters.Count == 1 &&
                     p.Parameters[0].Type != TypeFactory.GetType(typeof(CancellationToken))))
            if (loopMethods.Parameters[0].Type != TypeFactory.GetType(typeof(CancellationToken)))
                meta.InsertStatement(
                    $"{loopMethods.Name}Command = StatusContext.RunNonBlockingTaskCommand<{loopMethods.Parameters[0].Type}>({loopMethods.Name});");
    }

    private string SplitCamelCase(string str)
    {
        //https://stackoverflow.com/questions/5796383/insert-spaces-between-words-on-a-camel-cased-token
        return Regex.Replace(Regex.Replace(str, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2"), @"(\p{Ll})(\P{Ll})", "$1 $2");
    }
}