Migrating Code

This commit is contained in:
Jordan Wages 2024-04-11 02:10:29 -05:00
parent 1fbc49884c
commit cbbe897d15
9 changed files with 674 additions and 0 deletions

4
CapyKit/.editorconfig Normal file
View file

@ -0,0 +1,4 @@
[*.cs]
# CS8625: Cannot convert null literal to non-nullable reference type.
dotnet_diagnostic.CS8625.severity = none

View file

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Attributes
{
/// <summary>
/// Custom attribute class for decorating enumeration fields with additional data.
/// </summary>
/// <typeparam name="T">
/// Generic type parameter allowing for arbitrary declarations and assignments of meaning.
/// </typeparam>
[AttributeUsage(AttributeTargets.Field)]
public abstract class EnumerationAttribute<T> : Attribute
{
#region Properties
/// <summary>
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}"/> class with a
/// specified value.
/// </summary>
/// <value> The value. </value>
public T Value { get; private set; }
#endregion
#region Constructors
/// <summary> Gets the value of the enumeration represented by this attribute. </summary>
/// <param name="value">
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}"/> class with a
/// specified value.
/// </param>
protected EnumerationAttribute(T value)
{
this.Value = value;
}
#endregion
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Attributes
{
/// <summary> An attribute class for decorating enumeration fields with a description. </summary>
/// <seealso cref="EnumerationAttribute{T}"/>
[AttributeUsage(AttributeTargets.Field)]
public class EnumerationDescriptionAttribute : EnumerationAttribute<string>
{
/// <summary>
/// Initializes a new instance of the <see cref="EnumerationDescriptionAttribute"/> class with
/// the specified description.
/// </summary>
/// <param name="description"> The description of the enumeration value. </param>
public EnumerationDescriptionAttribute(string description)
: base(description)
{
//
}
}
}

173
CapyKit/CapyEvent.cs Normal file
View file

@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using CapyKit.Extensions;
namespace CapyKit
{
/// <summary>
/// The CapyEventReporter class is responsible for managing event subscriptions and emissions within CapyKit.
/// </summary>
/// <remarks>
/// Because consumers of CapyKit may have varied ways of handling logging, the <see cref="CapyEventReporter"/> provides
/// a way for subscribers to recieve events for various "events" within the library. These can be thought of as
/// a logging solution for CapyKit.
/// </remarks>
public static class CapyEventReporter
{
#region Members
/// <summary>
/// A dictionary storing event handlers and their corresponding origins for each subscription level.
/// </summary>
private static Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>> subscribers = new Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>>();
#endregion
#region Methods
/// <summary>
/// Subscribes the specified event handler to the event with the given subscription level and
/// origin.
/// </summary>
/// <remarks>
/// If there is no existing list for the given subscription level, a new list is created and
/// added to the dictionary.
/// </remarks>
/// <param name="callback"> The event handler to subscribe. </param>
/// <param name="subscriptionLevel"> The severity level of the event to subscribe to. </param>
/// <param name="origin">
/// (Optional) The name of the method or class where the subscription is made.
/// </param>
public static void Subscribe(CapyEventHandler callback, EventLevel subscriptionLevel, [CallerMemberName] string origin = null)
{
if (!subscribers[subscriptionLevel].Any())
{
subscribers.Add(subscriptionLevel, new List<(CapyEventHandler Handler, string origin)>());
}
subscribers[subscriptionLevel].Add((callback, origin ?? "[Unknown]"));
}
/// <summary>
/// Unsubscribes the specified event handler from the event with the given origin.
/// </summary>
/// <param name="callback"> The event handler to unsubscribe. </param>
/// <param name="origin">
/// The name of the method or class where the subscription was made.
/// </param>
public static void Unsubscribe(CapyEventHandler callback, string origin)
{
foreach (var value in Enum.GetValues(typeof(EventLevel)))
{
if (value is EventLevel)
{
subscribers[(EventLevel)value].RemoveAll(c => c.Handler == callback && c.origin == origin);
}
}
}
/// <summary> Emits an event with the given severity level, message, and method name. </summary>
/// <remarks>
/// In order to allow for efficient calling member access via <see cref="CallerMemberNameAttribute"/>
/// , it is suggested that <paramref name="args"/> is defined explicitly for formatted messages.
/// </remarks>
/// <param name="eventLevel"> The severity level of the event. </param>
/// <param name="message"> The message describing the reason for the event. </param>
/// <param name="method">
/// (Optional) The name of the method where the event was raised. String formatting for <paramref name="args"/>
/// is accepted.
/// </param>
/// <param name="args">
/// A variable-length parameters list containing arguments for formatting the message.
/// </param>
/// <example>
/// CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.",
/// args: new[] { enumeration });
/// </example>
internal static void EmitEvent(EventLevel eventLevel, string message, [CallerMemberName] string method = null, params object[] args)
{
if (!subscribers.ContainsKey(eventLevel))
{
return;
}
var formattedMessage = string.Format(message, args);
var capyEventArgs = new CapyEventArgs(eventLevel, formattedMessage, method);
foreach (var subscriber in subscribers[eventLevel])
{
subscriber.Handler(capyEventArgs);
}
}
#endregion
}
/// <summary>
/// A delegate representing an event handler that accepts a <see cref="CapyEventArgs"/> instance.
/// </summary>
/// <param name="e">The CapyEventArgs instance containing event data.</param>
public delegate void CapyEventHandler(CapyEventArgs e);
/// <summary>
/// The CapyEventArgs class represents an event argument instance with event level, message, and
/// method name information.
/// </summary>
public class CapyEventArgs : EventArgs
{
#region Properties
/// <summary>
/// Gets the severity level of the event.
/// </summary>
public EventLevel Level { get; private set; }
/// <summary>
/// Gets the message describing the reason for the event.
/// </summary>
public string Message { get; private set; }
/// <summary>
/// Gets the name of the method where the event was raised.
/// </summary>
public string MethodName { get; private set; }
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the CapyEventArgs class with the specified event level, message, and method name.
/// </summary>
/// <param name="level">The severity level of the event.</param>
/// <param name="message">A descriptive message explaining the reason for the event.</param>
/// <param name="method">The name of the method where the event was raised.</param>
public CapyEventArgs(EventLevel level, string message, string method = null)
{
this.Level = level;
this.Message = message;
this.MethodName = method ?? "[Unknown]";
}
#endregion
}
/// <summary> Enumeration representing different event level severity values. </summary>
public enum EventLevel
{
/// <summary> Represents a critical error that requires immediate attention. </summary>
Critical = 0,
/// <summary> Represents an error that prevents the normal execution of the application. </summary>
Error = 1,
/// <summary> Represents informational messages that provide useful context to the consumer. </summary>
Information = 2,
/// <summary> Represents detailed messages that are typically used for debugging purposes. </summary>
Debug = 3
}
}

9
CapyKit/CapyKit.csproj Normal file
View file

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

30
CapyKit/CapyKit.sln Normal file
View file

@ -0,0 +1,30 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34408.163
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CapyKit", "CapyKit.csproj", "{D1ACE10F-CBC8-4BA8-BB85-11DB4EEE5912}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BE8D96CA-FC33-4F28-AF49-0E4AEC6D3FD9}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D1ACE10F-CBC8-4BA8-BB85-11DB4EEE5912}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1ACE10F-CBC8-4BA8-BB85-11DB4EEE5912}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1ACE10F-CBC8-4BA8-BB85-11DB4EEE5912}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1ACE10F-CBC8-4BA8-BB85-11DB4EEE5912}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AF075174-308F-4D69-9160-C3CCE89EAD68}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,93 @@
using CapyKit.Attributes;
using CapyKit.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace CapyKit.Extensions
{
public static class EnumerationExtensions
{
#region Methods
/// <summary>
/// A <typeparamref name="T"/> extension method that parses a string into an enumeration.
/// </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="enumeration"> The enumeration to act on. </param>
/// <param name="value"> The value. </param>
/// <returns> A T. </returns>
public static T Parse<T>(this T enumeration, string value) where T : Enum
{
return (T)Enum.Parse(typeof(T), value);
}
/// <summary>
/// A <typeparamref name="T"/> extension method that parses a string into an enumeration.
/// </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="enumeration"> The enumeration to act on. </param>
/// <param name="value"> The string value of the <see cref="Enum"/>. </param>
/// <param name="ignoreCase"> True to ignore case. </param>
/// <returns> A T. </returns>
public static T Parse<T>(this T enumeration, string value, bool ignoreCase) where T : Enum
{
return (T)Enum.Parse(typeof(T), value, ignoreCase);
}
/// <summary>
/// An <see cref="Enum"/> extension method that gets an integer value representing the enumation.
/// </summary>
/// <param name="enumeration"> The enumeration to act on. </param>
/// <returns> The integer value of the enumeration. </returns>
public static int GetValue(this Enum enumeration)
{
return (int)Convert.ChangeType(enumeration, TypeCode.Int32);
}
/// <summary> An <see cref="Enum"/> extension method that gets a name. </summary>
/// <param name="enumeration"> The enumeration to act on. </param>
/// <returns> The name of the enumeration. </returns>
public static string GetName(this Enum enumeration)
{
return Enum.GetName(enumeration.GetType(), enumeration) ?? "[Unknown]";
}
/// <summary> An <see cref="Enum"/> extension method that gets a human readable name. </summary>
/// <param name="enumeration"> The enumeration to act on. </param>
/// <returns> The human readable name of the enumeration. </returns>
public static string GetPrettyName(this Enum enumeration)
{
return LanguageHelper.CamelCaseToHumanReadable(GetName(enumeration));
}
/// <summary> An <see cref="Enum"/> extension method that gets a description. </summary>
/// <param name="enumeration"> The enumeration to act on. </param>
/// <returns>
/// The description if available, otherwise the string representation of the enumeration.
/// </returns>
public static string GetDescription(this Enum enumeration)
{
var memInfo = enumeration.GetType().GetMember(enumeration.GetName());
if (memInfo.Any())
{
var attribute = memInfo.First().GetCustomAttribute(typeof(EnumerationDescriptionAttribute)) as EnumerationDescriptionAttribute;
if (attribute == null)
{
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.", args: new[] { enumeration });
}
else
{
return attribute.Value;
}
}
return enumeration.ToString();
}
#endregion Methods
}
}

View file

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace CapyKit.Helpers
{
public class LanguageHelper
{
#region Methods
/// <summary> Converts camel case text to human readable text. </summary>
/// <param name="value"> The value. </param>
/// <returns> A string in human readable format. </returns>
public static string CamelCaseToHumanReadable(string value)
{
var regex = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace);
return regex.Replace(value, " ");
}
#endregion
}
}

271
CapyKit/Pool.cs Normal file
View file

@ -0,0 +1,271 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace CapyKit
{
/// <summary>
/// A managed pool of resources. This class provides a thread-safe way to manage a collection of
/// objects of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T"> The type of objects to be managed by the pool. </typeparam>
public class Pool<T>
{
#region Members
/// <summary> The collection of pooled items. </summary>
private readonly ConcurrentBag<PoolItem<T>> poolItemCollection;
/// <summary> (Immutable) The number of items in the pool. </summary>
private readonly int poolSize;
#endregion Members
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified pool size.
/// </summary>
/// <param name="poolSize"> The size of the pool. </param>
public Pool(int poolSize)
{
this.poolSize = poolSize;
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
FillPoolItemCollection(poolSize);
}
/// <summary>
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified pool size
/// and constructor selector.
/// </summary>
/// <param name="poolSize"> The size of the pool. </param>
/// <param name="constructorSelector">
/// The constructor selector used to create new instances of <typeparamref name="T"/>.
/// </param>
public Pool(int poolSize, Func<T> constructorSelector)
{
this.poolSize = poolSize;
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
FillPoolItemCollection(poolSize, constructorSelector);
}
/// <summary>
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified collection
/// of items.
/// </summary>
/// <param name="collection">
/// The collection of <typeparamref name="T"/> items with which to seed the pool.
/// </param>
public Pool(IEnumerable<T> collection)
{
this.poolSize = collection.Count();
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
FillPoolItemCollection(collection);
}
#endregion Constructors
#region Methods
/// <summary>
/// Initializes the pool with the specified number of items using the default constructor.
/// </summary>
/// <param name="poolSize"> The size of the pool. </param>
private void FillPoolItemCollection(int poolSize)
{
FillPoolItemCollection(poolSize, () => default(T));
}
/// <summary>
/// Initializes the pool with the specified number of items using the specified constructor
/// selector.
/// </summary>
/// <param name="poolSize"> The size of the pool. </param>
/// <param name="constructorSelector"> The constructor selector. </param>
private void FillPoolItemCollection(int poolSize, Func<T> constructorSelector)
{
for (int i = 0; i < poolSize; i++)
{
this.poolItemCollection.Add(new PoolItem<T>(constructorSelector(), i));
}
}
/// <summary> Fill the pool item collection from an existing <typeparamref name="T"/> collection. </summary>
/// <param name="collection"> The <typeparamref name="T"/> collection. </param>
private void FillPoolItemCollection(IEnumerable<T> collection)
{
int index = 0;
foreach (var item in collection)
{
this.poolItemCollection.Add(new PoolItem<T>(item, index++));
}
}
/// <summary> Gets the first available item from the pool and sets its lock. </summary>
/// <returns> The first available item from the pool. </returns>
public PoolItem<T> GetAvailableItem()
{
lock (this.poolItemCollection)
{
if (this.poolItemCollection.Any(item => !item.Locked))
{
var firstAvailableItem = this.poolItemCollection.First(item => !item.Locked);
firstAvailableItem.SetLock();
CapyEventReporter.EmitEvent(EventLevel.Debug, "Accessed ppol and retrieved {0}", args: new[] { firstAvailableItem });
return firstAvailableItem;
}
}
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not return an available item.");
return null;
}
/// <summary> Releases the lock on the specified item and returns it to the pool. </summary>
/// <remarks>
/// This method sets the <see cref="PoolItem{T}.Locked"/> flag to <see langword="false"/> so that
/// it can be retrieved by <see cref="Pool{T}.GetAvailableItem"/>.
/// </remarks>
/// <param name="item"> The item to release. </param>
public void ReleaseItem(PoolItem<T> item)
{
item.ReleaseLock();
}
#endregion Methods
}
/// <summary> A pool item. This class cannot be inherited. </summary>
/// <typeparam name="T"> The type of the pooled item. </typeparam>
public sealed class PoolItem<T>
{
#region Members
/// <summary> The pooled item. </summary>
private readonly T item;
/// <summary> A flag indicating whether the item is locked or not. </summary>
private bool locked;
/// <summary> The zero-based index of the pooled item. </summary>
private readonly int index;
/// <summary> The name of the pooled item <see cref="Type"/>. </summary>
private readonly string typeName;
#endregion Members
#region Properties
/// <summary> Gets the pooled resource. </summary>
/// <value> The pooled resource. </value>
public T Item
{
get
{
return this.item;
}
}
/// <summary> Gets a value indicating whether this object is locked or not. </summary>
/// <value> A value indicating whether this object is locked or not. </value>
public bool Locked
{
get
{
return this.locked;
}
}
/// <summary> Gets the zero-based index of the pooled item. </summary>
/// <value> The index. </value>
public int Index
{
get
{
return this.index;
}
}
/// <summary> Gets the name of the <see cref="Type"/> of the pooled item. </summary>
/// <value> The name of the <see cref="Type"/> of the pooled item. </value>
public string TypeName
{
get
{
return this.typeName;
}
}
#endregion Properties
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="PoolItem{T}"/> class with the specified item and
/// index.
/// </summary>
/// <param name="item"> The pooled item. </param>
/// <param name="index"> The zero-based index of the pooled item. </param>
internal PoolItem(T item, int index)
{
this.item = item;
this.index = index;
this.locked = false;
this.typeName = typeof(T).Name;
}
#endregion Constructors
#region Methods
/// <summary> Sets the lock on the item indicating that it is in use. </summary>
/// <remarks> If the item is already locked, an error event is emitted. </remarks>
/// <returns>
/// <see langword="true"/> if the item is locked successfully, <see langword="false"/> if it
/// fails.
/// </returns>
public bool SetLock()
{
if (this.locked)
{
CapyEventReporter.EmitEvent(EventLevel.Error, "Lock requested for {0}, but the lock request failed.", args: new[] { this });
return false;
}
this.locked = true;
return true;
}
/// <summary> Releases the lock on the item. </summary>
/// <remarks> If the item is not locked, an error event is emitted. </remarks>
public void ReleaseLock()
{
if (!this.locked)
{
CapyEventReporter.EmitEvent(EventLevel.Error, "Lock release requested for {0}, but the lock was already released.", args: new[] { this } );
}
this.locked = false;
}
#endregion Methods
#region Overrides
/// <summary> Returns a string that represents the current object and its lock state. </summary>
/// <returns> A string that represents the current object and its lock state. </returns>
public override string ToString()
{
return string.Format("{0} {1} ({2})", this.typeName, this.index, this.locked ? "Locked" : "Unlocked");
}
#endregion Overrides
}
}