rename Pilz.Plugins.Advanced to Pilz.Features

This commit is contained in:
Pilzinsel64
2025-11-06 11:49:46 +01:00
parent 8bc09d9a59
commit fb42b0675b
37 changed files with 66 additions and 50 deletions

View File

@@ -0,0 +1,3 @@
namespace Pilz.Features;
public delegate void PluginFeatureExecuteEventHandler(object sender, PluginFeatureExecuteEventArgs e);

View File

@@ -0,0 +1,5 @@
namespace Pilz.Features.Exceptions;
public class PluginFeatureException : Exception;
public class PluginFeatureNotFoundException : PluginFeatureException;

View File

@@ -0,0 +1,34 @@
namespace Pilz.Features;
public static class Extensions
{
public static T? ExecuteIfEnabled<T>(this PluginFunction @this, params object?[]? @params)
{
return @this.Enabled ? @this.Execute<T>(@params) : default;
}
public static object? ExecuteIfEnabled(this PluginFunction @this, params object?[]? @params)
{
return @this.Enabled ? @this.Execute(@params) : default;
}
public static T? ExecuteIfEnabled<T>(this PluginFunction @this, PluginFunctionSimpleParamter? @params)
{
return @this.Enabled ? @this.Execute<T>(@params) : default;
}
public static object? ExecuteIfEnabled(this PluginFunction @this, PluginFunctionParameter? @params)
{
return @this.Enabled ? @this.Execute(@params) : default;
}
public static IEnumerable<T> Enabled<T>(this IEnumerable<T> @this) where T : PluginFeature
{
return @this.Where(n => n.Enabled);
}
public static IEnumerable<T> Ordered<T>(this IEnumerable<T> @this) where T : PluginFeature
{
return @this.OrderByDescending(n => n.Prioritization);
}
}

View File

@@ -0,0 +1,8 @@
namespace Pilz.Features;
public enum FeaturePrioritization
{
Low = -1,
Default = 0,
High = 1,
}

View File

@@ -0,0 +1,5 @@
namespace Pilz.Features;
public interface IPluginFeatureConstructor
{
}

View File

@@ -0,0 +1,12 @@
namespace Pilz.Features;
public interface IPluginFeatureProvider
{
static abstract PluginFeature Instance { get; }
}
public interface IPluginFeatureProvider<T> : IPluginFeatureProvider where T : PluginFeature, IPluginFeatureProvider<T>
{
static new abstract T Instance { get; }
static PluginFeature IPluginFeatureProvider.Instance => Instance;
}

View File

@@ -0,0 +1,6 @@
namespace Pilz.Features;
public interface IPluginFeaturesProvider
{
static abstract PluginFeature[] GetFeatures();
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<Version>2.11.0</Version>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,110 @@
namespace Pilz.Features;
public abstract class PluginFeature
{
/// <summary>
/// Fires when the plugin feature gets used.
/// <br/>- For <see cref="PluginFunction"/> this fires on <see cref="PluginFunction.ExecuteFunction(PluginFunctionParameter?)"/>.
/// <br/>- For <see cref="T:PluginModule"/> this fires on <see cref="T:PluginModule.CreateNewUI(PluginFunctionParameter?)"/>.
/// <br/>- For any other custom feature implementation this may variate.
/// </summary>
public static event PluginFeatureExecuteEventHandler? OnExecute;
/// <summary>
/// The type of the feature defines where the feature get integrated.
/// </summary>
public string Type { get; init; }
/// <summary>
/// The identifier of the feature should be uniquie for the current <see cref="Type"/>.
/// It defines a feature within a type.
/// </summary>
public string Identifier { get; init; }
/// <summary>
/// The full identifier of the feature should be uniquie and is the combination of <see cref="Type"/> and <see cref="Identifier"/>.
/// It defines a feature across all types.
/// </summary>
public string FullIdentifier => GetFullIdentifier(Type, Identifier);
/// <summary>
/// The (display) name of the feature.
/// </summary>
public virtual string? Name { get; init; }
/// <summary>
/// The (display) description of the feature.
/// </summary>
public virtual string? Description { get; init; }
/// <summary>
/// The symbol for the feature.
/// </summary>
public virtual object? Icon { get; set; }
/// <summary>
/// Sets the prioritization of the feature.
/// This will be respected on abfragen features and on inserting as items using the extension methods"/>.
/// Some applications might implement a way to regonize feature prioritization via its own way.
/// </summary>
public virtual FeaturePrioritization Prioritization { get; set; }
/// <summary>
/// Defines if the feature is enabled/visible.
/// </summary>
public virtual bool Enabled { get; set; } = true;
protected PluginFeature(string type, string identifier)
{
Identifier = identifier;
Type = type;
}
protected PluginFeature(string type, string identifier, string? name) : this(type, identifier)
{
Name = name;
}
public static string GetFullIdentifier(string featureType, string identifier)
{
return $"{featureType}:{identifier}";
}
/// <summary>
/// Fires the <see cref="OnExecute"/> event with the status <see cref="PluginFeatureExecuteEventType.PostEvent"/>.
/// </summary>
/// <param name="params">The present <see cref="PluginFunctionParameter"/>.</param>
/// <param name="result">The resulting object that will be returned.</param>
/// <returns>Returns true if the original function should be executed.</returns>
protected bool OnPreExecute(PluginFunctionParameter? @params, ref object? result)
{
if (OnExecute != null)
{
var args = new PluginFeatureExecuteEventArgs(PluginFeatureExecuteEventType.PreEvent, @params);
OnExecute.Invoke(this, args);
if (args.Handled)
{
result = args.Result;
return false;
}
}
return true;
}
/// <summary>
/// Fires the <see cref="OnExecute"/> event with the status <see cref="PluginFeatureExecuteEventType.PostEvent"/>.
/// </summary>
/// <param name="params">The present <see cref="PluginFunctionParameter"/>.</param>
/// <param name="result">The resulting object that will be returned.</param>
protected void OnPostExecute(PluginFunctionParameter? @params, ref object? result)
{
if (OnExecute != null)
{
var args = new PluginFeatureExecuteEventArgs(PluginFeatureExecuteEventType.PostEvent, @params)
{
Result = result
};
OnExecute.Invoke(this, args);
if (args.Handled)
result = args.Result;
}
}
}

View File

@@ -0,0 +1,301 @@
using System.Reflection;
namespace Pilz.Features;
public class PluginFeatureController
{
// D e l e g a t e s
public delegate void PluginFeatureEventHandler(PluginFeatureController controller, PluginFeature feature);
// S t a t i c E v e n t s
/// <summary>
/// Fires when a new <see cref="PluginFeature"/> has been registred.
/// </summary>
public static event PluginFeatureEventHandler? OnPluginFeatureReistred;
/// <summary>
/// Fires when a <see cref="PluginFeature"/> has been unregistred.
/// </summary>
public static event PluginFeatureEventHandler? OnPluginFeatureUnregistred;
// S t a t i c M e m b e r s
protected static readonly string nameGetFeatures = $"{nameof(IPluginFeaturesProvider.GetFeatures)}";
protected static readonly string nameGetFeaturesExplicit = $"{typeof(IPluginFeaturesProvider).FullName}.{nameof(IPluginFeaturesProvider.GetFeatures)}";
protected static readonly string nameInstance = $"get_{nameof(IPluginFeatureProvider.Instance)}";
protected static readonly string nameInstnaceExplicit = $"{typeof(IPluginFeaturesProvider).FullName}.get_{nameof(IPluginFeatureProvider.Instance)}";
/// <summary>
/// The default public instance that can be used by plugins and the interface providing software.
/// </summary>
public static PluginFeatureController Instance { get; private set; } = new();
// I n s t a n c e M e m e b e r s
private readonly HashSet<PluginFeature> features = [];
/// <summary>
/// A wrapper of all registred <see cref="PluginFeature"/> instances.
/// </summary>
public FeatureController Features { get; init; }
/// <summary>
/// A wrapper for all registred <see cref="PluginModule"/> instances.
/// </summary>
public ModuleController Modules { get; init; }
/// <summary>
/// A wrapper for all registred <see cref="PluginFunction"/> instances.
/// </summary>
public FunctionController Functions { get; init; }
public PluginFeatureController()
{
Features = new(this);
Functions = new(this);
Modules = new(this);
}
/// <summary>
/// Registers all features found in the currently executing Assembly via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
/// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
/// </summary>
public void RegisterAllOwn(string? @namespace = null)
{
RegisterAll(Assembly.GetCallingAssembly());
}
/// <summary>
/// Registers all features found in the given <see cref="Assembly[]"/> via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
/// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
/// </summary>
/// <param name="assemblies">The assemblies to query for types.</param>
/// <param name="namespace">If not null, only types within the given namespace will be registered.</param>
public void RegisterAll(Assembly[] assemblies, string? @namespace = null)
{
foreach (var assembly in assemblies)
RegisterAll(assembly);
}
/// <summary>
/// Registers all features found in the given <see cref="Assembly"/> via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
/// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
/// </summary>
/// <param name="assembly">The assembly to query for types.</param>
/// <param name="namespace">If not null, only types within the given namespace will be registered.</param>
public void RegisterAll(Assembly assembly, string? @namespace = null)
{
RegisterAll(assembly.GetTypes());
}
/// <summary>
/// Registers all features found from the given <see cref="Type[]"/> via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
/// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
/// </summary>
/// <param name="types">The types to query for providers.</param>
/// <param name="namespace">If not null, the types will only be processed if they're within the given namespace.</param>
public void RegisterAll(Type[] types, string? @namespace = null)
{
foreach (var type in types)
RegisterAll(type);
}
/// <summary>
/// Registers all features found from the given <see cref="Type"/> via <see cref="IPluginFeatureProvider"/>, <see cref="IPluginFeatureProvider{T}"/> and <see cref="IPluginFeaturesProvider"/>.
/// <para><b>Note:</b><br/>Explicit implementations of <see cref="IPluginFeatureProvider{T}.Instance"/> can not be detected. For this case just implement <see cref="IPluginFeatureProvider.Instance"/> instead.</para>
/// </summary>
/// <param name="type">The type to query for providers.</param>
/// <param name="namespace">If not null, the type will only be processed if it's within the given namespace.</param>
public void RegisterAll(Type type, string? @namespace = null)
{
if (@namespace != null && type.Namespace != null && type.Namespace != @namespace && !type.Namespace.StartsWith(@namespace + ".") || type.IsAbstract)
return;
if (type.IsAssignableTo(typeof(IPluginFeaturesProvider)))
{
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var method = methods.FirstOrDefault(n => n.Name == nameGetFeaturesExplicit || n.Name == nameGetFeatures);
if (method != null && method.Invoke(null, null) is PluginFeature[] features)
{
foreach (var feature in features)
Register(feature);
}
}
else if (type.IsAssignableTo(typeof(IPluginFeatureProvider)))
{
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var method = methods.FirstOrDefault(n => n.Name == nameInstnaceExplicit || n.Name == nameInstance);
if (method != null && method.Invoke(null, null) is PluginFeature feature)
Register(feature);
}
else if (type.IsAssignableTo(typeof(IPluginFeatureConstructor)))
{
if (type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, [typeof(PluginFeatureController)]) is ConstructorInfo ctor1)
{
if (ctor1.Invoke([this]) is PluginFeature feature)
Register(feature);
}
else if (type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, []) is ConstructorInfo ctor)
{
if (ctor.Invoke([]) is PluginFeature feature)
Register(feature);
}
}
}
/// <summary>
/// Registers a feature via the given <see cref="IPluginFeatureProvider"/> or <see cref="IPluginFeatureProvider{T}"/>.
/// </summary>
public void Register<TProvider>() where TProvider : IPluginFeatureProvider
{
Register(TProvider.Instance);
}
/// <summary>
/// Registers all features via the given <see cref="IPluginFeaturesProvider"/>.
/// </summary>
public void RegisterAll<TProvider>() where TProvider : IPluginFeaturesProvider
{
foreach (var feature in TProvider.GetFeatures())
Register(feature);
}
/// <summary>
/// Registers the given feature.
/// </summary>
/// <param name="module"></param>
public void Register(PluginFeature module)
{
if (features.Add(module))
OnPluginFeatureReistred?.Invoke(this, module);
}
/// <summary>
/// Unregisters the given feature.
/// </summary>
/// <param name="module"></param>
public void Unregister(PluginFeature module)
{
features.Remove(module);
OnPluginFeatureUnregistred?.Invoke(this, module);
}
public class FeatureController(PluginFeatureController controller)
{
protected readonly PluginFeatureController controller = controller;
public virtual IEnumerable<PluginFeature> GetAll()
{
return [.. controller.features];
}
public virtual IEnumerable<T> GetAll<T>() where T : PluginFeature
{
return controller.features.OfType<T>();
}
public virtual T? Get<T>() where T : PluginFeature
{
return GetAll<T>().FirstOrDefault();
}
public virtual T EnsureGet<T>() where T : PluginFeature
{
if (Get<T>() is T feature)
return feature;
throw new PluginFeatureNotFoundException();
}
public virtual IEnumerable<PluginFeature> Get(string featureType)
{
return controller.features.Where(n => n.Type == featureType);
}
public virtual PluginFeature? GetFirst(string featureType)
{
return controller.features.FirstOrDefault(n => n.Type == featureType);
}
public virtual PluginFeature? GetByIdentifier(string fullIdentifier)
{
return controller.features.FirstOrDefault(n => n.FullIdentifier == fullIdentifier);
}
public virtual PluginFeature? GetByIdentifier(string featureType, string identifier)
{
return controller.features.FirstOrDefault(n => n.Type == featureType && n.Identifier == identifier);
}
}
public class FeatureController<T>(PluginFeatureController controller) : FeatureController(controller) where T : PluginFeature
{
public override IEnumerable<T> GetAll()
{
return GetAll<T>();
}
public override IEnumerable<T> Get(string moduleType)
{
return GetAll().Where(n => n.Type == moduleType);
}
public override T? GetFirst(string moduleType)
{
return base.GetFirst(moduleType) as T;
}
public override T? GetByIdentifier(string fullIdentifier)
{
return base.GetByIdentifier(fullIdentifier) as T;
}
public override T? GetByIdentifier(string featureType, string identifier)
{
return base.GetByIdentifier(featureType, identifier) as T;
}
}
public class ModuleController(PluginFeatureController controller) : FeatureController<PluginModuleBase>(controller)
{
}
public class FunctionController(PluginFeatureController controller) : FeatureController<PluginFunction>(controller)
{
public void ExecuteAll(string functionType)
{
foreach (var function in Get(functionType))
function.Execute();
}
public void ExecuteAll(string functionType, params object?[]? @params)
{
foreach (var function in Get(functionType))
function.Execute(@params);
}
public void ExecuteAll(string functionType, PluginFunctionParameter @params)
{
foreach (var function in Get(functionType))
function.Execute(@params);
}
public IEnumerable<object?> ExcuteAndGetResults(string functionType)
{
return Get(functionType).Select(n => n.Execute());
}
public IEnumerable<object?> ExcuteAndGetResults(string functionType, params object?[]? @params)
{
return Get(functionType).Select(n => n.Execute(@params));
}
public IEnumerable<object?> ExcuteAndGetResults(string functionType, PluginFunctionParameter @params)
{
return Get(functionType).Select(n => n.Execute(@params));
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Pilz.Features;
public class PluginFeatureExecuteEventArgs(PluginFeatureExecuteEventType eventType, PluginFunctionParameter? parameters) : EventArgs
{
public PluginFeatureExecuteEventType EventType { get; } = eventType;
public PluginFunctionParameter? Parameters { get; } = parameters;
public bool Handled { get; set; }
public object? Result { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace Pilz.Features;
public enum PluginFeatureExecuteEventType
{
PreEvent,
PostEvent,
}

View File

@@ -0,0 +1,48 @@
namespace Pilz.Features;
public abstract class PluginFunction : PluginFeature
{
protected PluginFunction(string type, string identifier) : base(type, identifier)
{
}
protected PluginFunction(string type, string identifier, string? name) : base(type, identifier, name)
{
}
public virtual object? Execute()
{
return Execute((PluginFunctionParameter?)null);
}
public virtual T? Execute<T>(params object?[]? @params)
{
return Execute<T>(new PluginFunctionSimpleParamter(@params));
}
public virtual object? Execute(params object?[]? @params)
{
return Execute(new PluginFunctionSimpleParamter(@params));
}
public virtual T? Execute<T>(PluginFunctionSimpleParamter? @params)
{
if (Execute(@params) is T result)
return result;
return default;
}
public virtual object? Execute(PluginFunctionParameter? @params)
{
object? result = default;
if (OnPreExecute(@params, ref result))
result = ExecuteFunction(@params);
OnPostExecute(@params, ref result);
return result;
}
protected abstract object? ExecuteFunction(PluginFunctionParameter? @params);
}

View File

@@ -0,0 +1,5 @@
namespace Pilz.Features;
public class PluginFunctionParameter
{
}

View File

@@ -0,0 +1,11 @@
namespace Pilz.Features;
public sealed class PluginFunctionSimpleParamter : PluginFunctionParameter
{
public object?[]? Params { get; init; }
public PluginFunctionSimpleParamter(params object?[]? @params)
{
Params = @params;
}
}

View File

@@ -0,0 +1,15 @@
using System.ComponentModel;
namespace Pilz.Features;
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class PluginModuleBase : PluginFeature
{
protected PluginModuleBase(string type, string identifier) : base(type, identifier)
{
}
protected PluginModuleBase(string type, string identifier, string? name) : base(type, identifier, name)
{
}
}