From 96414f4b65e0fd35de2532cf9e9a5d8004c47d23 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Fri, 19 Jan 2024 17:57:21 +0100
Subject: [PATCH 01/40] Execution logic modifications
---
.../CSF.Samples.Console.csproj | 2 +-
.../RequireOperationSystemAttribute.cs | 2 +-
.../CSF.Samples.Hosting.csproj | 2 +-
.../Components/IComponent.cs | 0
.../Components/IConditionalComponent.cs | 0
.../Components/IParameterComponent.cs | 0
.../Components/IParameterContainer.cs | 0
.../Contexts/ICommandContext.cs | 9 +-
.../ExecutionException.cs} | 6 +-
.../{Entities => Abstractions}/ModuleBase.cs | 23 ++++
.../PreconditionAttribute.cs | 10 +-
src/CSF.Core/Abstractions/Results/IResult.cs | 12 ++
src/CSF.Core/Abstractions/TypeReader.cs | 50 +++++++
.../CommandAttribute.cs | 0
.../ComplexAttribute.cs | 0
.../DescriptionAttribute.cs | 0
.../DontRegisterAttribute.cs | 0
.../GroupAttribute.cs | 0
.../PrimaryConstructorAttribute.cs | 0
.../PriorityAttribute.cs | 0
.../RemainderAttribute.cs | 0
.../RequireContextAttribute.cs | 2 +-
src/CSF.Core/CSF.Core.csproj | 4 +-
src/CSF.Core/CommandBuildingConfiguration.cs | 63 +++++----
src/CSF.Core/CommandManager.cs | 128 +++++++++++++++---
src/CSF.Core/CommandManagerHelper.cs | 95 +++++++++++++
.../Implementation => Components}/Command.cs | 3 +-
.../CommandCell.cs | 0
.../ComplexParameter.cs | 0
.../Constructor.cs | 0
.../Implementation => Components}/Module.cs | 2 +-
.../Parameter.cs | 0
src/CSF.Core/Contexts/CommandContext.cs | 11 ++
.../Entities/Contexts/IStringBasedContext.cs | 18 ---
.../Contexts/Implementation/CommandContext.cs | 33 -----
src/CSF.Core/Entities/Results/FailureCode.cs | 33 -----
src/CSF.Core/Entities/Results/IResult.cs | 18 ---
.../Results/Implementations/FailedResult.cs | 59 --------
.../Results/Implementations/SuccessResult.cs | 36 -----
.../Entities/TypeReaders/TypeReader.cs | 117 ----------------
.../CheckException.cs | 4 +-
.../CommandException.cs} | 6 +-
src/CSF.Core/Exceptions/MatchException.cs | 16 +++
.../ReadException.cs | 4 +-
.../SearchException.cs | 4 +-
...xecutionOptions.cs => ExecutionOptions.cs} | 18 ++-
.../Extensions/Internal/BuildHelper.cs | 8 +-
src/CSF.Core/Manager/Check.cs | 35 -----
src/CSF.Core/Manager/Execute.cs | 86 ------------
src/CSF.Core/Manager/Match.cs | 67 ---------
src/CSF.Core/Manager/Read.cs | 89 ------------
src/CSF.Core/Manager/Search.cs | 72 ----------
.../Parsing/Implementation/TextParser.cs | 0
src/CSF.Core/{Entities => }/Parsing/Parser.cs | 0
.../{Entities => }/Parsing/ParserCell.cs | 0
src/CSF.Core/Results/CheckResult.cs | 15 ++
src/CSF.Core/Results/MatchResult.cs | 29 ++++
src/CSF.Core/Results/ReadResult.cs | 24 ++++
src/CSF.Core/Results/RunResult.cs | 27 ++++
src/CSF.Core/Results/SearchResult.cs | 24 ++++
.../BaseTypeReader.cs | 0
.../ColorTypeReader.cs | 0
.../EnumTypeReader.cs | 0
.../TimeSpanTypeReader.cs | 0
src/CSF.Hosting/CSF.Hosting.csproj | 2 +-
src/CSF.Hosting/HostedCommandManager.cs | 2 +-
.../CSF.Tests.Benchmarks.csproj | 2 +-
67 files changed, 520 insertions(+), 752 deletions(-)
rename src/CSF.Core/{Entities => Abstractions}/Components/IComponent.cs (100%)
rename src/CSF.Core/{Entities => Abstractions}/Components/IConditionalComponent.cs (100%)
rename src/CSF.Core/{Entities => Abstractions}/Components/IParameterComponent.cs (100%)
rename src/CSF.Core/{Entities => Abstractions}/Components/IParameterContainer.cs (100%)
rename src/CSF.Core/{Entities => Abstractions}/Contexts/ICommandContext.cs (54%)
rename src/CSF.Core/{Entities/Exceptions/PipelineException.cs => Abstractions/ExecutionException.cs} (51%)
rename src/CSF.Core/{Entities => Abstractions}/ModuleBase.cs (70%)
rename src/CSF.Core/{Entities/Attributes => Abstractions}/PreconditionAttribute.cs (85%)
create mode 100644 src/CSF.Core/Abstractions/Results/IResult.cs
create mode 100644 src/CSF.Core/Abstractions/TypeReader.cs
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/CommandAttribute.cs (100%)
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/ComplexAttribute.cs (100%)
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/DescriptionAttribute.cs (100%)
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/DontRegisterAttribute.cs (100%)
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/GroupAttribute.cs (100%)
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/PrimaryConstructorAttribute.cs (100%)
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/PriorityAttribute.cs (100%)
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/RemainderAttribute.cs (100%)
rename src/CSF.Core/{Entities/Attributes/Implementation => Attributes}/RequireContextAttribute.cs (89%)
create mode 100644 src/CSF.Core/CommandManagerHelper.cs
rename src/CSF.Core/{Entities/Components/Implementation => Components}/Command.cs (98%)
rename src/CSF.Core/{Entities/Components/Implementation => Components}/CommandCell.cs (100%)
rename src/CSF.Core/{Entities/Components/Implementation => Components}/ComplexParameter.cs (100%)
rename src/CSF.Core/{Entities/Components/Implementation => Components}/Constructor.cs (100%)
rename src/CSF.Core/{Entities/Components/Implementation => Components}/Module.cs (97%)
rename src/CSF.Core/{Entities/Components/Implementation => Components}/Parameter.cs (100%)
create mode 100644 src/CSF.Core/Contexts/CommandContext.cs
delete mode 100644 src/CSF.Core/Entities/Contexts/IStringBasedContext.cs
delete mode 100644 src/CSF.Core/Entities/Contexts/Implementation/CommandContext.cs
delete mode 100644 src/CSF.Core/Entities/Results/FailureCode.cs
delete mode 100644 src/CSF.Core/Entities/Results/IResult.cs
delete mode 100644 src/CSF.Core/Entities/Results/Implementations/FailedResult.cs
delete mode 100644 src/CSF.Core/Entities/Results/Implementations/SuccessResult.cs
delete mode 100644 src/CSF.Core/Entities/TypeReaders/TypeReader.cs
rename src/CSF.Core/{Entities/Exceptions/Implementation => Exceptions}/CheckException.cs (63%)
rename src/CSF.Core/{Entities/Exceptions/Implementation/ExecuteException.cs => Exceptions/CommandException.cs} (51%)
create mode 100644 src/CSF.Core/Exceptions/MatchException.cs
rename src/CSF.Core/{Entities/Exceptions/Implementation => Exceptions}/ReadException.cs (63%)
rename src/CSF.Core/{Entities/Exceptions/Implementation => Exceptions}/SearchException.cs (66%)
rename src/CSF.Core/{CommandExecutionOptions.cs => ExecutionOptions.cs} (53%)
delete mode 100644 src/CSF.Core/Manager/Check.cs
delete mode 100644 src/CSF.Core/Manager/Execute.cs
delete mode 100644 src/CSF.Core/Manager/Match.cs
delete mode 100644 src/CSF.Core/Manager/Read.cs
delete mode 100644 src/CSF.Core/Manager/Search.cs
rename src/CSF.Core/{Entities => }/Parsing/Implementation/TextParser.cs (100%)
rename src/CSF.Core/{Entities => }/Parsing/Parser.cs (100%)
rename src/CSF.Core/{Entities => }/Parsing/ParserCell.cs (100%)
create mode 100644 src/CSF.Core/Results/CheckResult.cs
create mode 100644 src/CSF.Core/Results/MatchResult.cs
create mode 100644 src/CSF.Core/Results/ReadResult.cs
create mode 100644 src/CSF.Core/Results/RunResult.cs
create mode 100644 src/CSF.Core/Results/SearchResult.cs
rename src/CSF.Core/{Entities/TypeReaders/Implementation => TypeReaders}/BaseTypeReader.cs (100%)
rename src/CSF.Core/{Entities/TypeReaders/Implementation => TypeReaders}/ColorTypeReader.cs (100%)
rename src/CSF.Core/{Entities/TypeReaders/Implementation => TypeReaders}/EnumTypeReader.cs (100%)
rename src/CSF.Core/{Entities/TypeReaders/Implementation => TypeReaders}/TimeSpanTypeReader.cs (100%)
diff --git a/examples/CSF.Samples.Console/CSF.Samples.Console.csproj b/examples/CSF.Samples.Console/CSF.Samples.Console.csproj
index d455298..e875e9f 100644
--- a/examples/CSF.Samples.Console/CSF.Samples.Console.csproj
+++ b/examples/CSF.Samples.Console/CSF.Samples.Console.csproj
@@ -2,7 +2,7 @@
Exe
- net7.0
+ net8.0
enable
enable
diff --git a/examples/CSF.Samples.Console/Preconditions/RequireOperationSystemAttribute.cs b/examples/CSF.Samples.Console/Preconditions/RequireOperationSystemAttribute.cs
index 61a18ab..eabaa42 100644
--- a/examples/CSF.Samples.Console/Preconditions/RequireOperationSystemAttribute.cs
+++ b/examples/CSF.Samples.Console/Preconditions/RequireOperationSystemAttribute.cs
@@ -11,7 +11,7 @@ public RequireOperatingSystemAttribute(PlatformID platform)
Platform = platform;
}
- public override Result Evaluate(ICommandContext context, Command command, IServiceProvider provider)
+ public override Result EvaluateAsync(ICommandContext context, Command command, IServiceProvider provider)
{
if (Environment.OSVersion.Platform == Platform)
return Success();
diff --git a/examples/CSF.Samples.Hosting/CSF.Samples.Hosting.csproj b/examples/CSF.Samples.Hosting/CSF.Samples.Hosting.csproj
index 6ce426b..fa2c405 100644
--- a/examples/CSF.Samples.Hosting/CSF.Samples.Hosting.csproj
+++ b/examples/CSF.Samples.Hosting/CSF.Samples.Hosting.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net8.0
enable
enable
diff --git a/src/CSF.Core/Entities/Components/IComponent.cs b/src/CSF.Core/Abstractions/Components/IComponent.cs
similarity index 100%
rename from src/CSF.Core/Entities/Components/IComponent.cs
rename to src/CSF.Core/Abstractions/Components/IComponent.cs
diff --git a/src/CSF.Core/Entities/Components/IConditionalComponent.cs b/src/CSF.Core/Abstractions/Components/IConditionalComponent.cs
similarity index 100%
rename from src/CSF.Core/Entities/Components/IConditionalComponent.cs
rename to src/CSF.Core/Abstractions/Components/IConditionalComponent.cs
diff --git a/src/CSF.Core/Entities/Components/IParameterComponent.cs b/src/CSF.Core/Abstractions/Components/IParameterComponent.cs
similarity index 100%
rename from src/CSF.Core/Entities/Components/IParameterComponent.cs
rename to src/CSF.Core/Abstractions/Components/IParameterComponent.cs
diff --git a/src/CSF.Core/Entities/Components/IParameterContainer.cs b/src/CSF.Core/Abstractions/Components/IParameterContainer.cs
similarity index 100%
rename from src/CSF.Core/Entities/Components/IParameterContainer.cs
rename to src/CSF.Core/Abstractions/Components/IParameterContainer.cs
diff --git a/src/CSF.Core/Entities/Contexts/ICommandContext.cs b/src/CSF.Core/Abstractions/Contexts/ICommandContext.cs
similarity index 54%
rename from src/CSF.Core/Entities/Contexts/ICommandContext.cs
rename to src/CSF.Core/Abstractions/Contexts/ICommandContext.cs
index f07e79c..1688d0a 100644
--- a/src/CSF.Core/Entities/Contexts/ICommandContext.cs
+++ b/src/CSF.Core/Abstractions/Contexts/ICommandContext.cs
@@ -6,13 +6,8 @@
public interface ICommandContext
{
///
- /// The command parameters.
+ /// The command options.
///
- public string[] Parameters { get; set; }
-
- ///
- /// The command name.
- ///
- public string Name { get; set; }
+ public IExecutionOptions Options { get; }
}
}
diff --git a/src/CSF.Core/Entities/Exceptions/PipelineException.cs b/src/CSF.Core/Abstractions/ExecutionException.cs
similarity index 51%
rename from src/CSF.Core/Entities/Exceptions/PipelineException.cs
rename to src/CSF.Core/Abstractions/ExecutionException.cs
index 6edcdc1..461453f 100644
--- a/src/CSF.Core/Entities/Exceptions/PipelineException.cs
+++ b/src/CSF.Core/Abstractions/ExecutionException.cs
@@ -1,14 +1,14 @@
namespace CSF
{
- public abstract class PipelineException : Exception
+ public abstract class ExecutionException : Exception
{
- public PipelineException(string message)
+ public ExecutionException(string message)
: base(message)
{
}
- public PipelineException(string message, Exception innerException = null)
+ public ExecutionException(string message, Exception innerException = null)
: base(message, innerException)
{
diff --git a/src/CSF.Core/Entities/ModuleBase.cs b/src/CSF.Core/Abstractions/ModuleBase.cs
similarity index 70%
rename from src/CSF.Core/Entities/ModuleBase.cs
rename to src/CSF.Core/Abstractions/ModuleBase.cs
index d1a0fd3..9da3e48 100644
--- a/src/CSF.Core/Entities/ModuleBase.cs
+++ b/src/CSF.Core/Abstractions/ModuleBase.cs
@@ -40,5 +40,28 @@ public abstract class ModuleBase
/// The message to send.
public virtual void Respond(string message)
=> Console.WriteLine(message);
+
+ public virtual CommandManager.RunResult ReturnTypeHandle(object value)
+ {
+ switch (value)
+ {
+ case Task task:
+ return new(Command, task);
+ case null:
+ return new(Command, null);
+ default:
+ throw new NotSupportedException("The return value of this command is not supported.");
+ }
+ }
+
+ public virtual async ValueTask BeforeExecuteAsync()
+ {
+
+ }
+
+ public virtual async ValueTask AfterExecuteAsync()
+ {
+
+ }
}
}
diff --git a/src/CSF.Core/Entities/Attributes/PreconditionAttribute.cs b/src/CSF.Core/Abstractions/PreconditionAttribute.cs
similarity index 85%
rename from src/CSF.Core/Entities/Attributes/PreconditionAttribute.cs
rename to src/CSF.Core/Abstractions/PreconditionAttribute.cs
index 172bc65..34b509d 100644
--- a/src/CSF.Core/Entities/Attributes/PreconditionAttribute.cs
+++ b/src/CSF.Core/Abstractions/PreconditionAttribute.cs
@@ -13,7 +13,7 @@ public abstract class PreconditionAttribute : Attribute
/// The command that is to be executed if this and all other precondition evaluations succeed.
/// The services in scope for the current command execution.
/// A result that represents the outcome of the evaluation.
- public abstract Result Evaluate(ICommandContext context, Command command, IServiceProvider services);
+ public abstract async Task EvaluateAsync(ICommandContext context, Command command);
///
/// Returns that the evaluation has failed.
@@ -28,14 +28,6 @@ protected static Result Failure(string reason)
protected static Result Success()
=> new();
- internal void EvalInternal(ICommandContext context, Command command, IServiceProvider services)
- {
- var result = Evaluate(context, command, services);
-
- if (!result.IsSuccess)
- throw new CheckException(result.Reason);
- }
-
///
/// Represents the result structure that displays the returned state of the evaluation.
///
diff --git a/src/CSF.Core/Abstractions/Results/IResult.cs b/src/CSF.Core/Abstractions/Results/IResult.cs
new file mode 100644
index 0000000..6135ab6
--- /dev/null
+++ b/src/CSF.Core/Abstractions/Results/IResult.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF
+{
+ public interface IResult
+ {
+ }
+}
diff --git a/src/CSF.Core/Abstractions/TypeReader.cs b/src/CSF.Core/Abstractions/TypeReader.cs
new file mode 100644
index 0000000..a6e206c
--- /dev/null
+++ b/src/CSF.Core/Abstractions/TypeReader.cs
@@ -0,0 +1,50 @@
+namespace CSF
+{
+ ///
+ /// Represents a generic to use for parsing provided types into the targetted type.
+ ///
+ /// The targetted type for this typereader.
+ public abstract class TypeReader : TypeReader
+ {
+ ///
+ public override Type Type { get; } = typeof(T);
+
+ ///
+ public override abstract Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value);
+ }
+
+ public abstract class TypeReader
+ {
+ ///
+ /// The type that this reader intends to return.
+ ///
+ public abstract Type Type { get; }
+
+ ///
+ /// Evaluates an input and tries to parse it into a value that matches the expected parameter type.
+ ///
+ /// The command context used to execute the command currently in scope.
+ /// The parameter this input evaluation is targetting.
+ /// The services in scope for the current command execution.
+ /// The input that this evaluation intends to convert into the expected parameter type.
+ /// A result that represents the outcome of the evaluation.
+ public abstract ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, object value);
+
+ ///
+ /// Gets a range of default s.
+ ///
+ /// A range of s that are defined in the library by default.
+ public static TypeReader[] CreateDefaultReaders()
+ {
+ var range = BaseTypeReader.CreateBaseReaders();
+
+ int length = range.Length;
+ Array.Resize(ref range, length + 2);
+
+ range[length++] = new TimeSpanTypeReader();
+ range[length++] = new ColorTypeReader();
+
+ return range;
+ }
+ }
+}
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/CommandAttribute.cs b/src/CSF.Core/Attributes/CommandAttribute.cs
similarity index 100%
rename from src/CSF.Core/Entities/Attributes/Implementation/CommandAttribute.cs
rename to src/CSF.Core/Attributes/CommandAttribute.cs
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/ComplexAttribute.cs b/src/CSF.Core/Attributes/ComplexAttribute.cs
similarity index 100%
rename from src/CSF.Core/Entities/Attributes/Implementation/ComplexAttribute.cs
rename to src/CSF.Core/Attributes/ComplexAttribute.cs
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/DescriptionAttribute.cs b/src/CSF.Core/Attributes/DescriptionAttribute.cs
similarity index 100%
rename from src/CSF.Core/Entities/Attributes/Implementation/DescriptionAttribute.cs
rename to src/CSF.Core/Attributes/DescriptionAttribute.cs
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/DontRegisterAttribute.cs b/src/CSF.Core/Attributes/DontRegisterAttribute.cs
similarity index 100%
rename from src/CSF.Core/Entities/Attributes/Implementation/DontRegisterAttribute.cs
rename to src/CSF.Core/Attributes/DontRegisterAttribute.cs
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/GroupAttribute.cs b/src/CSF.Core/Attributes/GroupAttribute.cs
similarity index 100%
rename from src/CSF.Core/Entities/Attributes/Implementation/GroupAttribute.cs
rename to src/CSF.Core/Attributes/GroupAttribute.cs
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/PrimaryConstructorAttribute.cs b/src/CSF.Core/Attributes/PrimaryConstructorAttribute.cs
similarity index 100%
rename from src/CSF.Core/Entities/Attributes/Implementation/PrimaryConstructorAttribute.cs
rename to src/CSF.Core/Attributes/PrimaryConstructorAttribute.cs
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/PriorityAttribute.cs b/src/CSF.Core/Attributes/PriorityAttribute.cs
similarity index 100%
rename from src/CSF.Core/Entities/Attributes/Implementation/PriorityAttribute.cs
rename to src/CSF.Core/Attributes/PriorityAttribute.cs
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/RemainderAttribute.cs b/src/CSF.Core/Attributes/RemainderAttribute.cs
similarity index 100%
rename from src/CSF.Core/Entities/Attributes/Implementation/RemainderAttribute.cs
rename to src/CSF.Core/Attributes/RemainderAttribute.cs
diff --git a/src/CSF.Core/Entities/Attributes/Implementation/RequireContextAttribute.cs b/src/CSF.Core/Attributes/RequireContextAttribute.cs
similarity index 89%
rename from src/CSF.Core/Entities/Attributes/Implementation/RequireContextAttribute.cs
rename to src/CSF.Core/Attributes/RequireContextAttribute.cs
index 62dc795..92f4fe9 100644
--- a/src/CSF.Core/Entities/Attributes/Implementation/RequireContextAttribute.cs
+++ b/src/CSF.Core/Attributes/RequireContextAttribute.cs
@@ -19,7 +19,7 @@ public RequireContextAttribute(Type contextType)
ContextType = contextType;
}
- public override Result Evaluate(ICommandContext context, Command command, IServiceProvider provider)
+ public override Result EvaluateAsync(ICommandContext context, Command command, IServiceProvider provider)
{
var providedType = context.GetType();
diff --git a/src/CSF.Core/CSF.Core.csproj b/src/CSF.Core/CSF.Core.csproj
index cea5f4e..c7b9756 100644
--- a/src/CSF.Core/CSF.Core.csproj
+++ b/src/CSF.Core/CSF.Core.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
enable
CSF
@@ -42,7 +42,7 @@
-
+
diff --git a/src/CSF.Core/CommandBuildingConfiguration.cs b/src/CSF.Core/CommandBuildingConfiguration.cs
index 6ee2774..d9ca317 100644
--- a/src/CSF.Core/CommandBuildingConfiguration.cs
+++ b/src/CSF.Core/CommandBuildingConfiguration.cs
@@ -2,30 +2,45 @@
namespace CSF
{
- ///
- /// Represents a context used to set up the command manager and its child containers.
- ///
- public sealed class CommandBuildingConfiguration
+ public sealed class FrameworkBuilder
{
- ///
- /// The assemblies to be used to register modules and typereaders automatically. Items will be registered when any of the following conditions are true:
- ///
- ///
- ///
- /// - The type inherits or , does not contain undeclared generic parameters and is public.
- /// - The type inherits or , does not contain undeclared generic parameters and is public.
- ///
- ///
- public Assembly[] RegistrationAssemblies { get; set; } = new[] { Assembly.GetEntryAssembly() };
-
- ///
- /// A range of typereaders that are to be manually registered to all existing readers.
- ///
- public TypeReader[] TypeReaders { get; set; } = Array.Empty();
-
- ///
- /// Gets the default configuration that is used when no is provided at manager creation.
- ///
- public static CommandBuildingConfiguration Default { get; } = new();
+ public List Assemblies { get; set; } = [ Assembly.GetEntryAssembly() ];
+
+ public List TypeReaders { get; set; } = [];
+
+ public FrameworkBuilder AddAssembly()
+ {
+ Assemblies.Add(Assembly.GetEntryAssembly());
+ return this;
+ }
+
+ public FrameworkBuilder AddAssembly(Assembly assembly)
+ {
+ Assemblies.Add(assembly);
+ return this;
+ }
+
+ public FrameworkBuilder WithAssemblies(params Assembly[] assemblies)
+ {
+ Assemblies.AddRange(assemblies);
+ return this;
+ }
+
+ public FrameworkBuilder AddTypeReader(TypeReader typeReader)
+ {
+ TypeReaders.Add(typeReader);
+ return this;
+ }
+
+ public FrameworkBuilder WithTypeReaders(params TypeReader[] typeReaders)
+ {
+ TypeReaders.AddRange(typeReaders);
+ return this;
+ }
+
+ public CommandManager Build()
+ {
+
+ }
}
}
diff --git a/src/CSF.Core/CommandManager.cs b/src/CSF.Core/CommandManager.cs
index 095d243..e10ecf5 100644
--- a/src/CSF.Core/CommandManager.cs
+++ b/src/CSF.Core/CommandManager.cs
@@ -1,4 +1,5 @@
[assembly: CLSCompliant(true)]
+
namespace CSF
{
///
@@ -7,35 +8,128 @@ namespace CSF
///
/// Guides and documentation can be found at:
///
- public partial class CommandManager
+ public class CommandManager
{
- private readonly IServiceProvider _services;
-
///
/// Gets the components registered to this manager.
///
public HashSet Components { get; }
- ///
- /// Creates a new with default configuration.
- ///
- /// The services to use to handle command execution and module registration.
- public CommandManager(IServiceProvider services)
- : this(services, CommandBuildingConfiguration.Default)
+ public async Task ExecuteAsync(ICommandContext context, object[] args)
{
+ // search all relevant commands.
+ var searches = Search(args);
+
+ // define a fallback for unsuccesful execution.
+ MatchResult? fallback = default;
+
+ // order searches by descending for priority definitions.
+ foreach (var search in searches.OrderByDescending(x => x.Command.Priority))
+ {
+ var match = await MatchAsync(context, search, args);
+ if (fallback is not null)
+ fallback = match;
+
+ if (match.Success)
+ return await RunAsync(context, match);
+ }
+
+ if (!fallback.HasValue)
+ return new SearchResult(new SearchException(""));
+
+ return fallback;
}
- ///
- /// Creates a new with provided configuration.
- ///
- /// The services used to handle command execution and module registration.
- /// The configuration that should be used to construct the manager.
- public CommandManager(IServiceProvider services, CommandBuildingConfiguration configuration)
+ public IEnumerable Search(object[] args)
{
- _services = services;
+ // recursively search for commands in the execution.
+ return Components.RecursiveSearch(args, 0);
+ }
+
+ public static async Task MatchAsync(ICommandContext context, SearchResult search, object[] args)
+ {
+ // check command preconditions.
+ var check = await CheckAsync(context, search.Command);
+
+ // verify check success, if not, return the failure.
+ if (!check.Success)
+ return new(search.Command, new MatchException("", check.Exception));
+
+ // read the command parameters in right order.
+ var readResult = await ReadAsync(context, search, args);
+
+ // exchange the reads for result, verifying successes in the process.
+ var reads = new object[readResult.Length];
+ for (int i = 0; i < readResult.Length; i++)
+ {
+ // check for read success.
+ if (!readResult[i].Success)
+ return new(search.Command, readResult[i].Exception);
+
+ reads[i] = readResult[i];
+ }
+
+ // return successful match if execution reaches here.
+ return new(search.Command, reads);
+ }
+
+ public static async Task ReadAsync(ICommandContext context, SearchResult search, object[] args)
+ {
+ // skip if no parameters exist.
+ if (!search.Command.HasParameters)
+ return [];
+
+ // determine height of search to discover command name.
+ var length = args.Length - search.SearchHeight;
+
+ // check if input equals command length.
+ if (search.Command.MaxLength == length)
+ return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0);
+
+ // check if input is longer than command, but remainder to concatenate.
+ if (search.Command.MaxLength <= length && search.Command.HasRemainder)
+ return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0);
+
+ // check if input is shorter than command, but optional parameters to replace.
+ if (search.Command.MaxLength > length && search.Command.MinLength <= length)
+ return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0);
+
+ // input is too long or too short.
+ return [];
+ }
+
+ public static async Task CheckAsync(ICommandContext context, Command command)
+ {
+ foreach (var precon in command.Preconditions)
+ {
+ if (!await precon.EvaluateAsync(context, command))
+ return new(new CheckException(""));
+ }
+ return new();
+ }
+
+ public static async Task RunAsync(ICommandContext context, MatchResult match)
+ {
+ try
+ {
+ var module = context.Options.Scope.ServiceProvider.GetService(match.Command.Module.Type) as ModuleBase;
+
+ module.Context = context;
+ module.Command = match.Command;
+
+ await module.BeforeExecuteAsync();
+
+ var value = match.Command.Target.Invoke(module, match.Reads);
+
+ await module.AfterExecuteAsync();
- Components = configuration.Build();
+ return module.ReturnTypeHandle(value);
+ }
+ catch (Exception exception)
+ {
+ return new(match.Command, exception);
+ }
}
}
}
diff --git a/src/CSF.Core/CommandManagerHelper.cs b/src/CSF.Core/CommandManagerHelper.cs
new file mode 100644
index 0000000..3321442
--- /dev/null
+++ b/src/CSF.Core/CommandManagerHelper.cs
@@ -0,0 +1,95 @@
+namespace CSF
+{
+ internal static class CommandManagerHelper
+ {
+ public static IEnumerable RecursiveSearch(this IEnumerable components, object[] args, int searchHeight)
+ {
+ List discovered = [];
+
+ // select command by name or alias.
+ var selection = components.Where(command => command.Aliases.Any(x => x == (string)args[searchHeight]));
+
+ foreach (var component in selection)
+ {
+ if (component is Module module)
+ {
+ // add the cluster found in the next iteration, if any.
+ var nested = module.Components.RecursiveSearch(args, searchHeight + 1);
+ discovered.AddRange(nested);
+ }
+ else
+ // add the top level matches immediately.
+ discovered.Add(new(component as Command, searchHeight));
+
+ // when the ranges fail, no results should return.
+ }
+
+ return discovered;
+ }
+
+ public static async Task RecursiveReadAsync(this IParameterComponent[] param, ICommandContext context, object[] args, int index)
+ {
+
+ static async ValueTask ReadAsync(IParameterComponent param, ICommandContext context, object arg)
+ {
+ if (arg.GetType() == param.Type)
+ return new(arg);
+
+ if (param.IsNullable && arg is null or "null" or "nothing")
+ return new(arg);
+
+ return await param.TypeReader.EvaluateAsync(context, param, arg);
+ }
+
+ var results = new ReadResult[param.Length];
+
+ for (int i = 0; i < param.Length; i++)
+ {
+ var parameter = param[i];
+
+ if (parameter.IsRemainder)
+ {
+ var input = string.Join(" ", args.Skip(index));
+ if (parameter.Type == typeof(string))
+ results[i] = new(input);
+ else
+ results[i] = await ReadAsync(parameter, context, input);
+
+ break;
+ }
+
+ if (parameter.IsOptional && args.Length <= index)
+ {
+ results[i] = new(Type.Missing);
+ continue;
+ }
+
+ if (parameter is ComplexParameter complex)
+ {
+ var result = await complex.Parameters.RecursiveReadAsync(context, args, index);
+
+ index += result.Length;
+
+ if (result.Any(x => !x.Success))
+ {
+ try
+ {
+ var obj = complex.Constructor.Target.Invoke(result.Select(x => x.Value).ToArray());
+ results[i] = new(obj);
+ }
+ catch (Exception ex)
+ {
+ results[i] = new(ex);
+ }
+ }
+ continue;
+ }
+
+ results[i] = await ReadAsync(parameter, context, args[index]);
+ index++;
+ }
+
+ return results;
+ }
+ }
+}
diff --git a/src/CSF.Core/Entities/Components/Implementation/Command.cs b/src/CSF.Core/Components/Command.cs
similarity index 98%
rename from src/CSF.Core/Entities/Components/Implementation/Command.cs
rename to src/CSF.Core/Components/Command.cs
index ec4e521..94bce24 100644
--- a/src/CSF.Core/Entities/Components/Implementation/Command.cs
+++ b/src/CSF.Core/Components/Command.cs
@@ -1,4 +1,5 @@
-using System.Reflection;
+using System.Dynamic;
+using System.Reflection;
namespace CSF
{
diff --git a/src/CSF.Core/Entities/Components/Implementation/CommandCell.cs b/src/CSF.Core/Components/CommandCell.cs
similarity index 100%
rename from src/CSF.Core/Entities/Components/Implementation/CommandCell.cs
rename to src/CSF.Core/Components/CommandCell.cs
diff --git a/src/CSF.Core/Entities/Components/Implementation/ComplexParameter.cs b/src/CSF.Core/Components/ComplexParameter.cs
similarity index 100%
rename from src/CSF.Core/Entities/Components/Implementation/ComplexParameter.cs
rename to src/CSF.Core/Components/ComplexParameter.cs
diff --git a/src/CSF.Core/Entities/Components/Implementation/Constructor.cs b/src/CSF.Core/Components/Constructor.cs
similarity index 100%
rename from src/CSF.Core/Entities/Components/Implementation/Constructor.cs
rename to src/CSF.Core/Components/Constructor.cs
diff --git a/src/CSF.Core/Entities/Components/Implementation/Module.cs b/src/CSF.Core/Components/Module.cs
similarity index 97%
rename from src/CSF.Core/Entities/Components/Implementation/Module.cs
rename to src/CSF.Core/Components/Module.cs
index 958e4a1..59fd6e1 100644
--- a/src/CSF.Core/Entities/Components/Implementation/Module.cs
+++ b/src/CSF.Core/Components/Module.cs
@@ -53,7 +53,7 @@ internal Module(Type type, IDictionary typeReaders, Module roo
Components = this.Build(typeReaders);
Name = expectedName ?? type.Name;
- Aliases = aliases ?? new string[] { Name };
+ Aliases = aliases ?? [ Name ];
}
///
diff --git a/src/CSF.Core/Entities/Components/Implementation/Parameter.cs b/src/CSF.Core/Components/Parameter.cs
similarity index 100%
rename from src/CSF.Core/Entities/Components/Implementation/Parameter.cs
rename to src/CSF.Core/Components/Parameter.cs
diff --git a/src/CSF.Core/Contexts/CommandContext.cs b/src/CSF.Core/Contexts/CommandContext.cs
new file mode 100644
index 0000000..547851d
--- /dev/null
+++ b/src/CSF.Core/Contexts/CommandContext.cs
@@ -0,0 +1,11 @@
+namespace CSF
+{
+ ///
+ /// Represents a class that's used to describe data from the command.
+ ///
+ public class CommandContext(T options) : ICommandContext
+ where T : IExecutionOptions
+ {
+ public IExecutionOptions Options { get; } = options;
+ }
+}
diff --git a/src/CSF.Core/Entities/Contexts/IStringBasedContext.cs b/src/CSF.Core/Entities/Contexts/IStringBasedContext.cs
deleted file mode 100644
index 265a27f..0000000
--- a/src/CSF.Core/Entities/Contexts/IStringBasedContext.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents an abstract context for text commands.
- ///
- public interface IStringBasedContext : ICommandContext
- {
- ///
- /// The raw input of the command.
- ///
- public string RawInput { get; set; }
-
- ///
- /// The flags present on the command input.
- ///
- public IReadOnlyDictionary NamedParameters { get; }
- }
-}
diff --git a/src/CSF.Core/Entities/Contexts/Implementation/CommandContext.cs b/src/CSF.Core/Entities/Contexts/Implementation/CommandContext.cs
deleted file mode 100644
index 68bf9b2..0000000
--- a/src/CSF.Core/Entities/Contexts/Implementation/CommandContext.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents a class that's used to describe data from the command.
- ///
- public class CommandContext : IStringBasedContext
- {
- ///
- public string Name { get; set; }
-
- ///
- public string[] Parameters { get; set; }
-
- ///
- public IReadOnlyDictionary NamedParameters { get; }
-
- ///
- public string RawInput { get; set; }
-
- public CommandContext(string rawInput, Parser parser = null)
- {
- parser ??= Parser.Text;
-
- var result = parser.Parse(rawInput);
-
- Name = result.Name;
- Parameters = result.Parameters;
- NamedParameters = result.NamedParameters;
-
- RawInput = rawInput;
- }
- }
-}
diff --git a/src/CSF.Core/Entities/Results/FailureCode.cs b/src/CSF.Core/Entities/Results/FailureCode.cs
deleted file mode 100644
index 0af3609..0000000
--- a/src/CSF.Core/Entities/Results/FailureCode.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents the error code that occurred during command execution. means there was no negative result.
- ///
- public enum FailureCode : int
- {
- ///
- /// Represents no negative result.
- ///
- Success,
-
- ///
- /// Represents a result tied to . Thrown when no command could be found.
- ///
- Search,
-
- ///
- /// Represents a result tied to . Thrown when no matched command succeeded its precondition checks.
- ///
- Check,
-
- ///
- /// Represents a result tied to . Thrown when no matched command succeeded parsing its parameters.
- ///
- Read,
-
- ///
- /// Represents a result tied to . Thrown when the command being executed failed to run its body.
- ///
- Execute,
- }
-}
diff --git a/src/CSF.Core/Entities/Results/IResult.cs b/src/CSF.Core/Entities/Results/IResult.cs
deleted file mode 100644
index 998b45a..0000000
--- a/src/CSF.Core/Entities/Results/IResult.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Runtime.CompilerServices;
-
-namespace CSF
-{
- ///
- /// Represents a
- ///
- public interface IResult
- {
- public TaskAwaiter GetAwaiter();
-
- ///
- /// Checks if the command from which this was formatted failed to execute or not.
- ///
- /// if the command failed to execute. if it succeeded.
- public bool Failed(out FailedResult result);
- }
-}
diff --git a/src/CSF.Core/Entities/Results/Implementations/FailedResult.cs b/src/CSF.Core/Entities/Results/Implementations/FailedResult.cs
deleted file mode 100644
index e45c49f..0000000
--- a/src/CSF.Core/Entities/Results/Implementations/FailedResult.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-
-namespace CSF
-{
- ///
- /// Represents a result that was produced by a command pipeline.
- ///
- public readonly struct FailedResult : IResult
- {
- ///
- /// Gets the code that represents where the pipeline failed.
- ///
- public FailureCode Code { get; } = FailureCode.Execute;
-
- ///
- /// The exception thrown by the pipeline in case of failure. In most cases, this is a . The is associated with the following exceptions:
- ///
- ///
- ///
- /// - - . Thrown when no command could be found.
- /// - - . Thrown when no matched command succeeded its precondition checks.
- /// - - . Thrown when no matched command succeeded parsing its parameters.
- /// - - . Thrown when the command being executed failed to run its body.
- ///
- /// This range is determined by the execution flow. Exceptions will occur in this order.
- ///
- public Exception Exception { get; } = null;
-
- ///
- /// Creates a new from the provided result code and exception.
- ///
- ///
- ///
- public FailedResult(FailureCode code, Exception exception = null)
- {
- Code = code;
- Exception = exception;
- }
-
- ///
- public bool Failed([NotNullWhen(true)] out FailedResult result)
- {
- result = this;
- return true;
- }
-
- ///
- public TaskAwaiter GetAwaiter()
- => Task.CompletedTask.GetAwaiter();
-
- ///
- /// Formats a readable output from this .
- ///
- ///
- public override string ToString()
- => $"[{Code}] " + Exception;
- }
-}
diff --git a/src/CSF.Core/Entities/Results/Implementations/SuccessResult.cs b/src/CSF.Core/Entities/Results/Implementations/SuccessResult.cs
deleted file mode 100644
index c71ce22..0000000
--- a/src/CSF.Core/Entities/Results/Implementations/SuccessResult.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-
-namespace CSF
-{
- public readonly struct SuccessResult : IResult
- {
- public Task CommandTask { get; }
-
- public SuccessResult(Task t)
- {
- CommandTask = t;
- }
-
- public SuccessResult()
- {
- CommandTask = Task.CompletedTask;
- }
-
- ///
- public bool Failed([NotNullWhen(true)] out FailedResult result)
- {
- result = default;
- return false;
- }
-
- ///
- public TaskAwaiter GetAwaiter()
- => CommandTask.GetAwaiter();
-
- public static implicit operator Task(SuccessResult result)
- {
- return result.CommandTask;
- }
- }
-}
diff --git a/src/CSF.Core/Entities/TypeReaders/TypeReader.cs b/src/CSF.Core/Entities/TypeReaders/TypeReader.cs
deleted file mode 100644
index 8e30142..0000000
--- a/src/CSF.Core/Entities/TypeReaders/TypeReader.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents a generic to use for parsing provided types into the targetted type.
- ///
- /// The targetted type for this typereader.
- public abstract class TypeReader : TypeReader
- {
- ///
- public override Type Type { get; } = typeof(T);
-
- ///
- public override abstract Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value);
- }
-
- public abstract class TypeReader
- {
- ///
- /// The type that this reader intends to return.
- ///
- public abstract Type Type { get; }
-
- ///
- /// Evaluates an input and tries to parse it into a value that matches the expected parameter type.
- ///
- /// The command context used to execute the command currently in scope.
- /// The parameter this input evaluation is targetting.
- /// The services in scope for the current command execution.
- /// The input that this evaluation intends to convert into the expected parameter type.
- /// A result that represents the outcome of the evaluation.
- public abstract Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value);
-
- ///
- /// Returns that the evaluation has failed.
- ///
- /// The reason why the evaluation failed.
- protected static Result Failure(string reason)
- => new(reason);
-
- ///
- /// Returns that the evaluation has succeeded.
- ///
- /// The value the evaluation returns upon success.
- protected static Result Success(object value)
- => new(value);
-
- internal object EvalInternal(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value)
- {
- var result = Evaluate(context, parameter, services, value);
-
- if (!result.IsSuccess)
- throw new ReadException(result.Reason);
-
- return result.Value;
- }
-
- ///
- /// Represents the result structure that displays the returned state of the evaluation.
- ///
- public readonly struct Result
- {
- ///
- /// Gets if the evaluation was successful or not.
- ///
- public bool IsSuccess { get; }
-
- ///
- /// Gets the value the evaluation returned if it succeeded.
- ///
- public object Value { get; }
-
- ///
- /// Gets the reason of failure in case it has.
- ///
- public string Reason { get; }
-
- ///
- /// Creates a new successful evaluation result.
- ///
- /// The value that the evaluation is supposed to return upon success.
- public Result(object value)
- {
- IsSuccess = true;
- Reason = null;
- Value = value;
- }
-
- ///
- /// Creates a new failed evaluation result.
- ///
- /// The reason why the evaluation has failed.
- public Result(string reason)
- {
- IsSuccess = false;
- Reason = reason;
- Value = null;
- }
- }
-
- ///
- /// Gets a range of default s.
- ///
- /// A range of s that are defined in the library by default.
- public static TypeReader[] CreateDefaultReaders()
- {
- var range = BaseTypeReader.CreateBaseReaders();
-
- int length = range.Length;
- Array.Resize(ref range, length + 2);
-
- range[length++] = new TimeSpanTypeReader();
- range[length++] = new ColorTypeReader();
-
- return range;
- }
- }
-}
diff --git a/src/CSF.Core/Entities/Exceptions/Implementation/CheckException.cs b/src/CSF.Core/Exceptions/CheckException.cs
similarity index 63%
rename from src/CSF.Core/Entities/Exceptions/Implementation/CheckException.cs
rename to src/CSF.Core/Exceptions/CheckException.cs
index 4a14afe..fbed9b3 100644
--- a/src/CSF.Core/Entities/Exceptions/Implementation/CheckException.cs
+++ b/src/CSF.Core/Exceptions/CheckException.cs
@@ -1,9 +1,9 @@
namespace CSF
{
///
- /// Represents a that is thrown when no matched command succeeded its precondition checks.
+ /// Represents a that is thrown when no matched command succeeded its precondition checks.
///
- public sealed class CheckException : PipelineException
+ public sealed class CheckException : ExecutionException
{
public CheckException(string message, Exception innerException = null)
: base(message, innerException)
diff --git a/src/CSF.Core/Entities/Exceptions/Implementation/ExecuteException.cs b/src/CSF.Core/Exceptions/CommandException.cs
similarity index 51%
rename from src/CSF.Core/Entities/Exceptions/Implementation/ExecuteException.cs
rename to src/CSF.Core/Exceptions/CommandException.cs
index f0b6363..5657a9f 100644
--- a/src/CSF.Core/Entities/Exceptions/Implementation/ExecuteException.cs
+++ b/src/CSF.Core/Exceptions/CommandException.cs
@@ -1,11 +1,11 @@
namespace CSF
{
///
- /// Represents a that is thrown when the command being executed failed to run its body.
+ /// Represents a that is thrown when the command being executed failed to run its body.
///
- public sealed class ExecuteException : PipelineException
+ public sealed class CommandException : ExecutionException
{
- public ExecuteException(string message, Exception innerException = null)
+ public CommandException(string message, Exception innerException = null)
: base(message, innerException)
{
diff --git a/src/CSF.Core/Exceptions/MatchException.cs b/src/CSF.Core/Exceptions/MatchException.cs
new file mode 100644
index 0000000..3bf1414
--- /dev/null
+++ b/src/CSF.Core/Exceptions/MatchException.cs
@@ -0,0 +1,16 @@
+namespace CSF
+{
+ public class MatchException : ExecutionException
+ {
+ public MatchException(string message, Exception innerException = null)
+ : base(message, innerException)
+ {
+
+ }
+
+ public override FailedResult AsResult()
+ {
+ return new(FailureCode.Execute, this);
+ }
+ }
+}
diff --git a/src/CSF.Core/Entities/Exceptions/Implementation/ReadException.cs b/src/CSF.Core/Exceptions/ReadException.cs
similarity index 63%
rename from src/CSF.Core/Entities/Exceptions/Implementation/ReadException.cs
rename to src/CSF.Core/Exceptions/ReadException.cs
index bf9ab51..d6f1802 100644
--- a/src/CSF.Core/Entities/Exceptions/Implementation/ReadException.cs
+++ b/src/CSF.Core/Exceptions/ReadException.cs
@@ -1,9 +1,9 @@
namespace CSF
{
///
- /// Represents a that is thrown when no matched command succeeded parsing its parameters.
+ /// Represents a that is thrown when no matched command succeeded parsing its parameters.
///
- public sealed class ReadException : PipelineException
+ public sealed class ReadException : ExecutionException
{
public ReadException(string message, Exception innerException = null)
: base(message, innerException)
diff --git a/src/CSF.Core/Entities/Exceptions/Implementation/SearchException.cs b/src/CSF.Core/Exceptions/SearchException.cs
similarity index 66%
rename from src/CSF.Core/Entities/Exceptions/Implementation/SearchException.cs
rename to src/CSF.Core/Exceptions/SearchException.cs
index 18a2fbb..517040a 100644
--- a/src/CSF.Core/Entities/Exceptions/Implementation/SearchException.cs
+++ b/src/CSF.Core/Exceptions/SearchException.cs
@@ -1,9 +1,9 @@
namespace CSF
{
///
- /// Represents a that is thrown when no command could be found.
+ /// Represents a that is thrown when no command could be found.
///
- public sealed class SearchException : PipelineException
+ public sealed class SearchException : ExecutionException
{
public SearchException(string message, Exception innerException = null)
: base(message, innerException)
diff --git a/src/CSF.Core/CommandExecutionOptions.cs b/src/CSF.Core/ExecutionOptions.cs
similarity index 53%
rename from src/CSF.Core/CommandExecutionOptions.cs
rename to src/CSF.Core/ExecutionOptions.cs
index 9ab25d4..e4fd49c 100644
--- a/src/CSF.Core/CommandExecutionOptions.cs
+++ b/src/CSF.Core/ExecutionOptions.cs
@@ -2,19 +2,27 @@
namespace CSF
{
+ public interface IExecutionOptions
+ {
+ ///
+ /// Gets or sets the service scope to be used in command execution. The services used for executing commands will be defaulted to the globally defined if this property is not set.
+ ///
+ public IServiceScope Scope { get; set; }
+ }
+
///
/// Represents a set of options that change the execution flow of the command handler.
///
- public class CommandExecutionOptions
+ public class ExecutionOptions : IExecutionOptions
{
///
/// Gets or sets the service scope to be used in command execution. The services used for executing commands will be defaulted to the globally defined if this property is not set.
///
public IServiceScope Scope { get; set; } = null;
- ///
- /// Gets the default options that are used across all executions that don't provide customized .
- ///
- public static CommandExecutionOptions Default { get; } = new();
+ public ExecutionOptions(IServiceProvider provider)
+ {
+ Scope = provider.CreateScope();
+ }
}
}
diff --git a/src/CSF.Core/Extensions/Internal/BuildHelper.cs b/src/CSF.Core/Extensions/Internal/BuildHelper.cs
index 1e077f1..7b26ac6 100644
--- a/src/CSF.Core/Extensions/Internal/BuildHelper.cs
+++ b/src/CSF.Core/Extensions/Internal/BuildHelper.cs
@@ -4,19 +4,19 @@ namespace CSF
{
internal static class BuildHelper
{
- public static HashSet Build(this CommandBuildingConfiguration context)
+ public static HashSet Build(this FrameworkBuilder context)
{
var modules = context.BuildModules();
return modules.SelectMany(x => x.Components).ToHashSet();
}
- public static IEnumerable BuildModules(this CommandBuildingConfiguration context)
+ public static IEnumerable BuildModules(this FrameworkBuilder context)
{
var typeReaders = TypeReader.CreateDefaultReaders().UnionBy(context.TypeReaders, x => x.Type).ToDictionary(x => x.Type, x => x);
var rootReader = typeof(TypeReader);
- foreach (var assembly in context.RegistrationAssemblies)
+ foreach (var assembly in context.Assemblies)
foreach (var type in assembly.GetTypes())
if (rootReader.IsAssignableFrom(type) && !type.IsAbstract && !type.ContainsGenericParameters)
{
@@ -30,7 +30,7 @@ public static IEnumerable BuildModules(this CommandBuildingConfiguration
}
var rootType = typeof(ModuleBase);
- foreach (var assembly in context.RegistrationAssemblies)
+ foreach (var assembly in context.Assemblies)
foreach (var type in assembly.GetTypes())
if (rootType.IsAssignableFrom(type) && !type.IsAbstract && !type.ContainsGenericParameters)
yield return new Module(type, typeReaders);
diff --git a/src/CSF.Core/Manager/Check.cs b/src/CSF.Core/Manager/Check.cs
deleted file mode 100644
index 3158d4c..0000000
--- a/src/CSF.Core/Manager/Check.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-namespace CSF
-{
- public partial class CommandManager
- {
- ///
- /// Checks all existing preconditions on the specified passed into the method.
- ///
- /// The command to check all preconditions for.
- /// The context containing information about the command input.
- /// The services in scope to execute the pipeline in relation to the provided context.
- /// An empty if the precondition evaluations succeeded. If any failed, the result of the first failed precondition.
- protected virtual FailedResult Check(Command command, ICommandContext context, IServiceProvider services)
- {
- try
- {
- command.Check(context, services);
-
- return new();
- }
- catch (PipelineException e)
- {
- return e.AsResult();
- }
- }
- }
-
- internal static class CheckOperations
- {
- public static void Check(this Command command, ICommandContext context, IServiceProvider services)
- {
- if (command.HasPreconditions)
- Parallel.ForEach(command.Preconditions, x => x.EvalInternal(context, command, services));
- }
- }
-}
diff --git a/src/CSF.Core/Manager/Execute.cs b/src/CSF.Core/Manager/Execute.cs
deleted file mode 100644
index 6b77552..0000000
--- a/src/CSF.Core/Manager/Execute.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-namespace CSF
-{
- public partial class CommandManager
- {
- ///
- /// Executes a command based on the input provided through the and options passed into this method.
- ///
- /// The context containing information about the command input.
- /// A range of options to customize command execution flow.
- /// An that can be awaited in asynchronous context.
- ///
- public virtual IResult ExecuteAsync(ICommandContext context, CommandExecutionOptions options = null)
- {
- options ??= CommandExecutionOptions.Default;
-
- var services = options.Scope?.ServiceProvider ?? _services;
-
- return ExecuteAsync(context, services);
- }
-
- ///
- /// Runs the command pipeline based on the input provided through the and options passed into this method.
- ///
- /// The context containing information about the command input.
- /// The services in scope to execute the pipeline in relation to the provided context.
- /// The result of the command, containing the state of failure or success.
- protected virtual IResult ExecuteAsync(ICommandContext context, IServiceProvider services)
- {
- try
- {
- var cell = Search(context, services);
-
- return ExecuteAsync(context, services, cell);
- }
- catch (PipelineException ex)
- {
- return ex.AsResult();
- }
- catch (Exception ex)
- {
- return new ExecuteException(ex.Message, ex).AsResult();
- }
- }
-
- ///
- /// Executes a resolved commandcell that succeeded through all relevant handles in the search and match pipeline.
- ///
- /// The context containing information about the command input.
- /// The services in scope to execute the pipeline in relation to the provided context.
- /// The resolved command that was chosen as the result out of the pipeline.
- /// The result of the command, containing the state of failure or success.
- ///
- /// Thrown when the return type of a command cannot be resolved.
- /// Consider overriding this method and adding the custom return type in the resolver.
- ///
- protected virtual IResult ExecuteAsync(ICommandContext context, IServiceProvider services, CommandCell cell)
- {
- var value = cell.Execute(context, services);
-
- switch (value)
- {
- case Task task:
- return new SuccessResult(task);
- case null:
- return new SuccessResult();
- default:
- throw new NotSupportedException("The return value of this command is not supported.");
- }
- }
- }
-
- internal static class ExecuteOperations
- {
- public static object Execute(this CommandCell cell, ICommandContext context, IServiceProvider services)
- {
- var module = services.GetService(cell.Command.Module.Type) as ModuleBase;
-
- module.Context = context;
- module.Command = cell.Command;
-
- var value = cell.Command.Target.Invoke(module, cell.Arguments);
-
- return value;
- }
- }
-}
diff --git a/src/CSF.Core/Manager/Match.cs b/src/CSF.Core/Manager/Match.cs
deleted file mode 100644
index e7d8253..0000000
--- a/src/CSF.Core/Manager/Match.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-namespace CSF
-{
- public partial class CommandManager
- {
- ///
- /// Runs through a found commands and attempts to match the amount of parameters to the represented amount in the context.
- ///
- /// The command
- /// The context containing information about the command input.
- /// The services in scope to execute the pipeline in relation to the provided context.
- /// A resolved match from the provided command, containing failure or result depending on the outcome of the evaluation.
- protected virtual CommandCell Match(Command command, ICommandContext context, IServiceProvider services)
- => command.Match(context, services);
-
- ///
- /// Runs through a set of found commands and tests them for the availability
- ///
- /// The range of components to attempt to match.
- /// The context containing information about the command input.
- /// The services in scope to execute the pipeline in relation to the provided context.
- /// All relevant matches to the provided context.
- protected virtual CommandCell[] Match(IEnumerable components, ICommandContext context, IServiceProvider services)
- => components.Match(context, services)
- .ToArray();
- }
-
- internal static class MatchOperations
- {
- public static IEnumerable Match(this IEnumerable components, ICommandContext context, IServiceProvider services)
- {
- foreach (var component in components)
- {
- if (component is not Command command)
- continue;
-
- var length = context.Parameters.Length;
-
- if (command.MaxLength == length)
- yield return command.Match(context, services);
-
- if (command.MaxLength <= length && command.HasRemainder)
- yield return command.Match(context, services);
-
- if (command.MaxLength > length && command.MinLength <= length)
- yield return command.Match(context, services);
- }
- }
-
- public static CommandCell Match(this Command command, ICommandContext context, IServiceProvider services)
- {
- try
- {
- command.Check(context, services);
-
- var arguments = command.HasParameters
- ? command.Parameters.Read(context, services)
- : Array.Empty
public ICommandContext Context { get; internal set; }
+ ///
+ /// Gets the command execution services as provided by the command scope.
+ ///
+ public IServiceProvider Services
+ {
+ get
+ => Context.Options.Scope.ServiceProvider;
+ }
+
///
/// Gets the component that displays all information about the command thats currently in scope.
///
@@ -41,7 +50,7 @@ public abstract class ModuleBase
public virtual void Respond(string message)
=> Console.WriteLine(message);
- public virtual CommandManager.RunResult ReturnTypeHandle(object value)
+ public virtual RunResult ReturnTypeHandle(object value)
{
switch (value)
{
diff --git a/src/CSF.Core/Abstractions/PreconditionAttribute.cs b/src/CSF.Core/Abstractions/PreconditionAttribute.cs
index 34b509d..db05341 100644
--- a/src/CSF.Core/Abstractions/PreconditionAttribute.cs
+++ b/src/CSF.Core/Abstractions/PreconditionAttribute.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using System.Diagnostics.CodeAnalysis;
+
+namespace CSF
{
///
/// Defines a precondition attribute.
@@ -6,6 +8,7 @@
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public abstract class PreconditionAttribute : Attribute
{
+ private static readonly string _exHeader = "Precondition result halted further command execution. View inner exception for more details.";
///
/// Evaluates a condition to handle a command and returns the result.
///
@@ -13,54 +16,31 @@ public abstract class PreconditionAttribute : Attribute
/// The command that is to be executed if this and all other precondition evaluations succeed.
/// The services in scope for the current command execution.
/// A result that represents the outcome of the evaluation.
- public abstract async Task EvaluateAsync(ICommandContext context, Command command);
+ public abstract ValueTask EvaluateAsync(ICommandContext context, Command command);
- ///
- /// Returns that the evaluation has failed.
- ///
- /// The reason why the evaluation failed.
- protected static Result Failure(string reason)
- => new(reason);
-
- ///
- /// Returns that the evaluation has succeeded.
- ///
- protected static Result Success()
- => new();
-
- ///
- /// Represents the result structure that displays the returned state of the evaluation.
- ///
- public readonly struct Result
+ public static CheckResult Error([DisallowNull] Exception exception)
{
- ///
- /// Gets if the evaluation was successful or not.
- ///
- public bool IsSuccess { get; }
+ if (exception == null)
+ ThrowHelpers.InvalidArg(exception);
- ///
- /// Gets the reason of failure in case it has.
- ///
- public string Reason { get; }
-
- ///
- /// Creates a new successful evaluation result.
- ///
- public Result()
+ if (exception is CheckException checkEx)
{
- IsSuccess = true;
- Reason = null;
+ return new(checkEx);
}
+ return new(new CheckException(_exHeader, exception));
+ }
- ///
- /// Creates a new failed evaluation result.
- ///
- /// The reason why the evaluation has failed.
- public Result(string reason)
- {
- IsSuccess = false;
- Reason = reason;
- }
+ public virtual CheckResult Error([DisallowNull] string error)
+ {
+ if (string.IsNullOrEmpty(error))
+ ThrowHelpers.InvalidArg(error);
+
+ return new(new CheckException(error));
+ }
+
+ public virtual CheckResult Success()
+ {
+ return new();
}
}
}
diff --git a/src/CSF.Core/Abstractions/TypeReader.cs b/src/CSF.Core/Abstractions/TypeReader.cs
index a6e206c..8811e0d 100644
--- a/src/CSF.Core/Abstractions/TypeReader.cs
+++ b/src/CSF.Core/Abstractions/TypeReader.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using System.Diagnostics.CodeAnalysis;
+
+namespace CSF
{
///
/// Represents a generic to use for parsing provided types into the targetted type.
@@ -10,11 +12,13 @@ public abstract class TypeReader : TypeReader
public override Type Type { get; } = typeof(T);
///
- public override abstract Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value);
+ public override abstract ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value);
}
public abstract class TypeReader
{
+ private static readonly string _exHeader = "TypeReader failed to parse provided value as '{0}'. View inner exception for more details.";
+
///
/// The type that this reader intends to return.
///
@@ -28,13 +32,45 @@ public abstract class TypeReader
/// The services in scope for the current command execution.
/// The input that this evaluation intends to convert into the expected parameter type.
/// A result that represents the outcome of the evaluation.
- public abstract ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, object value);
+ public abstract ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value);
- ///
- /// Gets a range of default s.
- ///
- /// A range of s that are defined in the library by default.
- public static TypeReader[] CreateDefaultReaders()
+ internal ValueTask ObjectEvaluateAsync(ICommandContext context, IParameterComponent parameter, object value)
+ {
+ if (value.GetType() == Type)
+ return ValueTask.FromResult(new ReadResult(value));
+
+ if (value is string str)
+ return EvaluateAsync(context, parameter, str);
+
+ return EvaluateAsync(context, parameter, value.ToString());
+ }
+
+ public virtual ReadResult Error([DisallowNull] Exception exception)
+ {
+ if (exception == null)
+ ThrowHelpers.InvalidArg(exception);
+
+ if (exception is ReadException readEx)
+ {
+ return new(readEx);
+ }
+ return new(new ReadException(string.Format(_exHeader, Type.Name), exception));
+ }
+
+ public virtual ReadResult Error([DisallowNull] string error)
+ {
+ if (string.IsNullOrEmpty(error))
+ ThrowHelpers.InvalidArg(error);
+
+ return new(new ReadException(error));
+ }
+
+ public virtual ReadResult Success(object value)
+ {
+ return new(value);
+ }
+
+ internal static TypeReader[] CreateDefaultReaders()
{
var range = BaseTypeReader.CreateBaseReaders();
diff --git a/src/CSF.Core/Attributes/CommandAttribute.cs b/src/CSF.Core/Attributes/CommandAttribute.cs
index 2e7962b..0764f11 100644
--- a/src/CSF.Core/Attributes/CommandAttribute.cs
+++ b/src/CSF.Core/Attributes/CommandAttribute.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using System.Diagnostics.CodeAnalysis;
+
+namespace CSF
{
///
/// An attribute that represents the required info to map a command.
@@ -20,8 +22,8 @@ public sealed class CommandAttribute : Attribute
/// Sets up a new command attribute with the provided name.
///
///
- public CommandAttribute(string name)
- : this(name, Array.Empty())
+ public CommandAttribute([DisallowNull] string name)
+ : this(name, [])
{
}
@@ -31,25 +33,19 @@ public CommandAttribute(string name)
///
///
[CLSCompliant(false)]
- public CommandAttribute(string name, params string[] aliases)
+ public CommandAttribute([DisallowNull] string name, params string[] aliases)
{
- static void Assign(ref string[] arr, string value, int pos)
- {
- Assert.IsNotEmpty(value);
- arr[pos] = value;
- pos++;
- }
+ if (string.IsNullOrWhiteSpace(name))
+ ThrowHelpers.InvalidArg(name);
- var i = 0;
- var array = new string[aliases.Length + 1];
+ var arr = new string[aliases.Length + 1];
- Assign(ref array, name, i);
+ arr[0] = name;
- foreach (var value in aliases)
- Assign(ref array, value, i);
+ Array.Copy(aliases, 0, arr, 1, aliases.Length);
Name = name;
- Aliases = array;
+ Aliases = arr;
}
}
}
diff --git a/src/CSF.Core/Attributes/DescriptionAttribute.cs b/src/CSF.Core/Attributes/DescriptionAttribute.cs
index bf3b280..0def0ec 100644
--- a/src/CSF.Core/Attributes/DescriptionAttribute.cs
+++ b/src/CSF.Core/Attributes/DescriptionAttribute.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using System.Diagnostics.CodeAnalysis;
+
+namespace CSF
{
///
/// Represents the description of a command.
@@ -15,9 +17,10 @@ public sealed class DescriptionAttribute : Attribute
/// Sets up a new with provided value.
///
///
- public DescriptionAttribute(string description)
+ public DescriptionAttribute([DisallowNull] string description)
{
- Assert.IsNotEmpty(description);
+ if (string.IsNullOrWhiteSpace(description))
+ ThrowHelpers.InvalidArg(description);
Description = description;
}
diff --git a/src/CSF.Core/Attributes/DontRegisterAttribute.cs b/src/CSF.Core/Attributes/DontRegisterAttribute.cs
index ef1f903..a2f8f42 100644
--- a/src/CSF.Core/Attributes/DontRegisterAttribute.cs
+++ b/src/CSF.Core/Attributes/DontRegisterAttribute.cs
@@ -3,7 +3,7 @@
///
/// Represents an attribute that forces the registration to not register provided member.
///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class DontRegisterAttribute : Attribute
{
diff --git a/src/CSF.Core/Attributes/GroupAttribute.cs b/src/CSF.Core/Attributes/GroupAttribute.cs
index 0ce4dc7..525e4b2 100644
--- a/src/CSF.Core/Attributes/GroupAttribute.cs
+++ b/src/CSF.Core/Attributes/GroupAttribute.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using System.Diagnostics.CodeAnalysis;
+
+namespace CSF
{
///
/// Represents a command group, functioning much like subcommands.
@@ -21,7 +23,7 @@ public sealed class GroupAttribute : Attribute
///
///
public GroupAttribute(string name)
- : this(name, Array.Empty())
+ : this(name, [])
{
}
@@ -32,25 +34,19 @@ public GroupAttribute(string name)
///
///
[CLSCompliant(false)]
- public GroupAttribute(string name, params string[] aliases)
+ public GroupAttribute([DisallowNull] string name, params string[] aliases)
{
- static void Assign(ref string[] arr, string value, int pos)
- {
- Assert.IsNotEmpty(value);
- arr[pos] = value;
- pos++;
- }
+ if (string.IsNullOrWhiteSpace(name))
+ ThrowHelpers.InvalidArg(name);
- var i = 0;
- var array = new string[aliases.Length + 1];
+ var arr = new string[aliases.Length + 1];
- Assign(ref array, name, i);
+ arr[0] = name;
- foreach (var value in aliases)
- Assign(ref array, value, i);
+ Array.Copy(aliases, 0, arr, 1, aliases.Length);
Name = name;
- Aliases = array;
+ Aliases = arr;
}
}
}
diff --git a/src/CSF.Core/Attributes/PriorityAttribute.cs b/src/CSF.Core/Attributes/PriorityAttribute.cs
index bdc6591..210853c 100644
--- a/src/CSF.Core/Attributes/PriorityAttribute.cs
+++ b/src/CSF.Core/Attributes/PriorityAttribute.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using System.Diagnostics.CodeAnalysis;
+
+namespace CSF
{
///
/// Represents an attribute that can prioritize one result over another when multiple matches were found.
@@ -6,23 +8,16 @@
///
/// By default, a command has a priority of 0. Higher values take priority, meaning a command with a priority of 1 will execute first if other commands have a priority of 0.
///
+ ///
+ /// Creates a new with provided priority.
+ ///
+ /// The priority of this command, which can be between 0 and 255.
[AttributeUsage(AttributeTargets.Method)]
- public sealed class PriorityAttribute : Attribute
+ public sealed class PriorityAttribute([DisallowNull] byte priority) : Attribute
{
///
/// Gets the priority of a command, where higher values take priority over lower ones.
///
- public byte Priority { get; }
-
- ///
- /// Creates a new with provided priority.
- ///
- /// The priority of this command, which can be between 0 and 255.
- public PriorityAttribute(byte priority)
- {
- Assert.IsNotNull(priority);
-
- Priority = priority;
- }
+ public byte Priority { get; } = priority;
}
}
diff --git a/src/CSF.Core/Attributes/RequireContextAttribute.cs b/src/CSF.Core/Attributes/RequireContextAttribute.cs
index 92f4fe9..380ae2f 100644
--- a/src/CSF.Core/Attributes/RequireContextAttribute.cs
+++ b/src/CSF.Core/Attributes/RequireContextAttribute.cs
@@ -3,30 +3,24 @@
///
/// Represents a precondition that checks if the provided context is valid.
///
- public sealed class RequireContextAttribute : PreconditionAttribute
+ ///
+ /// Compiles a new from provided .
+ ///
+ public sealed class RequireContextAttribute() : PreconditionAttribute
{
///
/// The context type to compare against.
///
- public Type ContextType { get; }
+ public Type ContextType { get; } = typeof(T);
- ///
- /// Compiles a new from provided .
- ///
- ///
- public RequireContextAttribute(Type contextType)
- {
- ContextType = contextType;
- }
-
- public override Result EvaluateAsync(ICommandContext context, Command command, IServiceProvider provider)
+ public override ValueTask EvaluateAsync(ICommandContext context, Command command)
{
var providedType = context.GetType();
if (providedType != ContextType)
- return Failure($"Invalid context was passed into the command. Expected: '{ContextType.FullName}', got '{providedType.FullName}'");
+ return ValueTask.FromResult(new CheckResult(new CheckException($"Invalid context was passed into the command. Expected: '{ContextType.FullName}', got '{providedType.FullName}'")));
- return Success();
+ return ValueTask.FromResult(new CheckResult());
}
}
}
diff --git a/src/CSF.Core/CMBuilder.cs b/src/CSF.Core/CMBuilder.cs
new file mode 100644
index 0000000..625e07b
--- /dev/null
+++ b/src/CSF.Core/CMBuilder.cs
@@ -0,0 +1,87 @@
+using System.Reflection;
+
+namespace CSF
+{
+ public sealed class CMBuilder
+ {
+ private bool _disposed = false;
+
+ public HashSet Assemblies { get; set; } = [ Assembly.GetEntryAssembly() ];
+
+ public HashSet TypeReaders { get; set; } = [];
+
+ public CMBuilder AddEntryAssembly()
+ {
+ if (_disposed)
+ ThrowHelpers.InvalidOp("This builder cannot be reused.");
+
+ Assemblies.Add(Assembly.GetEntryAssembly());
+ return this;
+ }
+
+ public CMBuilder AddAssembly(Assembly assembly)
+ {
+ if (_disposed)
+ ThrowHelpers.InvalidOp("This builder cannot be reused.");
+
+ Assemblies.Add(assembly);
+ return this;
+ }
+
+ public CMBuilder AddTypeReader(TypeReader typeReader)
+ {
+ if (_disposed)
+ ThrowHelpers.InvalidOp("This builder cannot be reused.");
+
+ TypeReaders.Add(typeReader);
+ return this;
+ }
+
+ public CommandManager Build()
+ {
+ var typeReaders = TypeReader.CreateDefaultReaders().UnionBy(TypeReaders, x => x.Type).ToDictionary(x => x.Type, x => x);
+
+ if (Assemblies.Count == 0)
+ ThrowHelpers.InvalidOp("An assembly has to be present in the builder prior to building the CommandManager.");
+
+ IEnumerable BuildComponents()
+ {
+ var rootReader = typeof(TypeReader);
+ foreach (var assembly in Assemblies)
+ {
+ foreach (var type in assembly.GetTypes())
+ {
+ if (rootReader.IsAssignableFrom(type)
+ && !type.IsAbstract
+ && !type.ContainsGenericParameters)
+ {
+ var reader = Activator.CreateInstance(type) as TypeReader;
+
+ // replace existing typereader with replacement handler
+ if (!typeReaders.TryAdd(reader.Type, reader))
+ typeReaders[reader.Type] = reader;
+ }
+ }
+ }
+
+ var rootType = typeof(ModuleBase);
+ foreach (var assembly in Assemblies)
+ {
+ foreach (var type in assembly.GetTypes())
+ {
+ if (rootType.IsAssignableFrom(type)
+ && !type.IsAbstract
+ && !type.ContainsGenericParameters)
+ {
+ yield return new Module(type, typeReaders);
+ }
+ }
+ }
+ }
+
+ _disposed = true;
+
+ return new(BuildComponents().SelectMany(x => x.Components), [.. Assemblies]);
+ }
+ }
+}
diff --git a/src/CSF.Core/CommandBuildingConfiguration.cs b/src/CSF.Core/CommandBuildingConfiguration.cs
deleted file mode 100644
index d9ca317..0000000
--- a/src/CSF.Core/CommandBuildingConfiguration.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Reflection;
-
-namespace CSF
-{
- public sealed class FrameworkBuilder
- {
- public List Assemblies { get; set; } = [ Assembly.GetEntryAssembly() ];
-
- public List TypeReaders { get; set; } = [];
-
- public FrameworkBuilder AddAssembly()
- {
- Assemblies.Add(Assembly.GetEntryAssembly());
- return this;
- }
-
- public FrameworkBuilder AddAssembly(Assembly assembly)
- {
- Assemblies.Add(assembly);
- return this;
- }
-
- public FrameworkBuilder WithAssemblies(params Assembly[] assemblies)
- {
- Assemblies.AddRange(assemblies);
- return this;
- }
-
- public FrameworkBuilder AddTypeReader(TypeReader typeReader)
- {
- TypeReaders.Add(typeReader);
- return this;
- }
-
- public FrameworkBuilder WithTypeReaders(params TypeReader[] typeReaders)
- {
- TypeReaders.AddRange(typeReaders);
- return this;
- }
-
- public CommandManager Build()
- {
-
- }
- }
-}
diff --git a/src/CSF.Core/CommandManager.cs b/src/CSF.Core/CommandManager.cs
index 2e83f6b..0e46f04 100644
--- a/src/CSF.Core/CommandManager.cs
+++ b/src/CSF.Core/CommandManager.cs
@@ -1,4 +1,6 @@
-[assembly: CLSCompliant(true)]
+using System.Reflection;
+
+[assembly: CLSCompliant(true)]
namespace CSF
{
@@ -8,14 +10,31 @@ namespace CSF
///
/// Guides and documentation can be found at:
///
- public class CommandManager
+ public sealed class CommandManager
{
///
/// Gets the components registered to this manager.
///
public HashSet Components { get; }
- public async Task ExecuteAsync(ICommandContext context, object[] args)
+ ///
+ /// Gets the assemblies used to register to this manager.
+ ///
+ public Assembly[] Assemblies { get; }
+
+ public CommandManager(IEnumerable components, Assembly[] assemblies)
+ {
+ Components = components.ToHashSet();
+ Assemblies = assemblies;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async ValueTask ExecuteAsync(ICommandContext context, params object[] args)
{
// search all relevant commands.
var searches = Search(args);
@@ -41,13 +60,19 @@ public async Task ExecuteAsync(ICommandContext context, object[] args)
return fallback;
}
+ ///
+ ///
+ ///
+ ///
+ ///
public IEnumerable Search(object[] args)
{
// recursively search for commands in the execution.
return Components.RecursiveSearch(args, 0);
}
- public static async Task MatchAsync(ICommandContext context, SearchResult search, object[] args)
+ #region Matching
+ internal static async ValueTask MatchAsync(ICommandContext context, SearchResult search, object[] args)
{
// check command preconditions.
var check = await CheckAsync(context, search.Command);
@@ -73,8 +98,10 @@ public static async Task MatchAsync(ICommandContext context, Search
// return successful match if execution reaches here.
return new(search.Command, reads);
}
+ #endregion
- public static async Task ReadAsync(ICommandContext context, SearchResult search, object[] args)
+ #region Reading
+ internal static async ValueTask ReadAsync(ICommandContext context, SearchResult search, object[] args)
{
// skip if no parameters exist.
if (!search.Command.HasParameters)
@@ -98,18 +125,24 @@ public static async Task ReadAsync(ICommandContext context, Search
// input is too long or too short.
return [];
}
-
- public static async Task CheckAsync(ICommandContext context, Command command)
+ #endregion
+
+ #region Checking
+ internal static async ValueTask CheckAsync(ICommandContext context, Command command)
{
foreach (var precon in command.Preconditions)
{
- if (!await precon.EvaluateAsync(context, command))
- return new(new CheckException(""));
+ var result = await precon.EvaluateAsync(context, command);
+
+ if (!result.Success)
+ return result;
}
return new();
}
+ #endregion
- public static async Task RunAsync(ICommandContext context, MatchResult match)
+ #region Running
+ internal static async ValueTask RunAsync(ICommandContext context, MatchResult match)
{
try
{
@@ -131,5 +164,6 @@ public static async Task RunAsync(ICommandContext context, MatchResul
return new(match.Command, exception);
}
}
+ #endregion
}
}
diff --git a/src/CSF.Core/CommandManagerHelper.cs b/src/CSF.Core/CommandManagerHelper.cs
index 3321442..250a93b 100644
--- a/src/CSF.Core/CommandManagerHelper.cs
+++ b/src/CSF.Core/CommandManagerHelper.cs
@@ -38,7 +38,7 @@ static async ValueTask ReadAsync(IParameterComponent param, ICommand
if (param.IsNullable && arg is null or "null" or "nothing")
return new(arg);
- return await param.TypeReader.EvaluateAsync(context, param, arg);
+ return await param.TypeReader.ObjectEvaluateAsync(context, param, arg);
}
var results = new ReadResult[param.Length];
diff --git a/src/CSF.Core/Components/Command.cs b/src/CSF.Core/Components/Command.cs
index 94bce24..2f570b1 100644
--- a/src/CSF.Core/Components/Command.cs
+++ b/src/CSF.Core/Components/Command.cs
@@ -57,12 +57,17 @@ internal Command(Module module, MethodInfo method, string[] aliases, IDictionary
{
var attributes = method.GetAttributes(true);
var preconditions = attributes.GetPreconditions();
- var parameters = method.BuildParameters(typeReaders);
+ var parameters = method.GetParameters(typeReaders);
var (minLength, maxLength) = parameters.GetLength();
if (parameters.Any(x => x.Attributes.Contains(false)))
- Assert.IsTrue(parameters[^1].IsRemainder, $"{nameof(RemainderAttribute)} can only exist on the last parameter of a method.");
+ {
+ if (parameters[^1].IsRemainder)
+ {
+ ThrowHelpers.InvalidOp($"{nameof(RemainderAttribute)} can only exist on the last parameter of a method.");
+ }
+ }
Priority = attributes.SelectFirstOrDefault()?.Priority ?? 0;
diff --git a/src/CSF.Core/Components/ComplexParameter.cs b/src/CSF.Core/Components/ComplexParameter.cs
index 3a3d40a..a1051ab 100644
--- a/src/CSF.Core/Components/ComplexParameter.cs
+++ b/src/CSF.Core/Components/ComplexParameter.cs
@@ -70,9 +70,12 @@ internal ComplexParameter(ParameterInfo parameterInfo, IDictionary 0, "Complex types are expected to have at least 1 constructor parameter.");
+ if (parameters.Length > 0)
+ {
+ ThrowHelpers.InvalidOp("Complex types are expected to have at least 1 constructor parameter.");
+ }
var (minLength, maxLength) = parameters.GetLength();
diff --git a/src/CSF.Core/Components/Module.cs b/src/CSF.Core/Components/Module.cs
index 59fd6e1..d10f978 100644
--- a/src/CSF.Core/Components/Module.cs
+++ b/src/CSF.Core/Components/Module.cs
@@ -50,7 +50,7 @@ internal Module(Type type, IDictionary typeReaders, Module roo
Preconditions = preconditions;
HasPreconditions = preconditions.Length > 0;
- Components = this.Build(typeReaders);
+ Components = this.GetComponents(typeReaders);
Name = expectedName ?? type.Name;
Aliases = aliases ?? [ Name ];
diff --git a/src/CSF.Core/Components/Parameter.cs b/src/CSF.Core/Components/Parameter.cs
index 1520923..d0851d6 100644
--- a/src/CSF.Core/Components/Parameter.cs
+++ b/src/CSF.Core/Components/Parameter.cs
@@ -57,7 +57,10 @@ internal Parameter(ParameterInfo parameterInfo, IDictionary ty
else
IsRemainder = false;
- if (Type != typeof(string) && Type != typeof(object))
+ if (Type.IsEnum)
+ TypeReader = new EnumTypeReader(Type);
+
+ else if (Type != typeof(string) && Type != typeof(object))
TypeReader = typeReaders[Type];
Attributes = attributes;
diff --git a/src/CSF.Core/Exceptions/CheckException.cs b/src/CSF.Core/Exceptions/CheckException.cs
index fbed9b3..13e8269 100644
--- a/src/CSF.Core/Exceptions/CheckException.cs
+++ b/src/CSF.Core/Exceptions/CheckException.cs
@@ -10,10 +10,5 @@ public CheckException(string message, Exception innerException = null)
{
}
-
- public override FailedResult AsResult()
- {
- return new(FailureCode.Check, this);
- }
}
}
diff --git a/src/CSF.Core/Exceptions/CommandException.cs b/src/CSF.Core/Exceptions/CommandException.cs
index 5657a9f..125404b 100644
--- a/src/CSF.Core/Exceptions/CommandException.cs
+++ b/src/CSF.Core/Exceptions/CommandException.cs
@@ -10,10 +10,5 @@ public CommandException(string message, Exception innerException = null)
{
}
-
- public override FailedResult AsResult()
- {
- return new(FailureCode.Execute, this);
- }
}
}
diff --git a/src/CSF.Core/Exceptions/MatchException.cs b/src/CSF.Core/Exceptions/MatchException.cs
index 3bf1414..cf6b1d0 100644
--- a/src/CSF.Core/Exceptions/MatchException.cs
+++ b/src/CSF.Core/Exceptions/MatchException.cs
@@ -7,10 +7,5 @@ public MatchException(string message, Exception innerException = null)
{
}
-
- public override FailedResult AsResult()
- {
- return new(FailureCode.Execute, this);
- }
}
}
diff --git a/src/CSF.Core/Exceptions/ReadException.cs b/src/CSF.Core/Exceptions/ReadException.cs
index d6f1802..2406df5 100644
--- a/src/CSF.Core/Exceptions/ReadException.cs
+++ b/src/CSF.Core/Exceptions/ReadException.cs
@@ -10,10 +10,5 @@ public ReadException(string message, Exception innerException = null)
{
}
-
- public override FailedResult AsResult()
- {
- return new(FailureCode.Read, this);
- }
}
}
diff --git a/src/CSF.Core/Exceptions/SearchException.cs b/src/CSF.Core/Exceptions/SearchException.cs
index 517040a..ec01158 100644
--- a/src/CSF.Core/Exceptions/SearchException.cs
+++ b/src/CSF.Core/Exceptions/SearchException.cs
@@ -10,10 +10,5 @@ public SearchException(string message, Exception innerException = null)
{
}
-
- public override FailedResult AsResult()
- {
- return new(FailureCode.Search, this);
- }
}
}
diff --git a/src/CSF.Core/Extensions/Internal/Assert.cs b/src/CSF.Core/Extensions/Internal/Assert.cs
deleted file mode 100644
index 84c66eb..0000000
--- a/src/CSF.Core/Extensions/Internal/Assert.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Runtime.CompilerServices;
-
-namespace CSF
-{
- internal static class Assert
- {
- public static void IsTrue(bool condition, string failureMessage)
- {
- if (!condition)
- throw new InvalidOperationException(failureMessage);
- }
-
- public static void IsFalse(bool condition, string failureMessage)
- {
- if (condition)
- throw new InvalidOperationException(failureMessage);
- }
-
- public static void IsNotNull(object value, [CallerArgumentExpression("value")] string caller = null)
- {
- if (value == null)
- throw new ArgumentNullException(paramName: caller, "Argument is null.");
- }
-
- public static void IsNotEmpty(string value, [CallerArgumentExpression("value")] string caller = null)
- {
- if (string.IsNullOrEmpty(value))
- throw new ArgumentNullException(paramName: caller, "Argument is null or empty.");
- }
-
- public static void IsNotWhitespace(string value, [CallerArgumentExpression("value")] string caller = null)
- {
- if (string.IsNullOrWhiteSpace(value))
- throw new ArgumentNullException(paramName: caller, "Argument is null or whitespace.");
- }
- }
-}
diff --git a/src/CSF.Core/Extensions/ServiceHelper.cs b/src/CSF.Core/Extensions/ServiceHelper.cs
deleted file mode 100644
index 61e855f..0000000
--- a/src/CSF.Core/Extensions/ServiceHelper.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using System.ComponentModel;
-
-namespace CSF
-{
- public static class ServiceHelper
- {
- ///
- /// Adds a range of command components to the collection based on the passed into this method.
- ///
- ///
- /// The building configuration by which commands are searched and registered.
- /// The same for chained calls.
- public static IServiceCollection AddComponents(this IServiceCollection collection, CommandBuildingConfiguration configuration)
- {
- var rootType = typeof(ModuleBase);
-
- foreach (var assembly in configuration.RegistrationAssemblies)
- foreach (var type in assembly.GetTypes())
- if (rootType.IsAssignableFrom(type) && !type.IsAbstract && !type.ContainsGenericParameters)
- collection.TryAddTransient(type);
-
- return collection;
- }
-
- ///
- /// Includes a into the this method is called on.
- ///
- ///
- /// The configuration required to set up a new instance of .
- /// The same for chained calls.
- public static IServiceCollection WithCommandManager(this IServiceCollection collection, Action action = null)
- {
- collection.WithCommandManager(action);
-
- return collection;
- }
-
- ///
- /// Includes inheriting into the this method is called on.
- ///
- /// The type inheriting to include in the collection.
- ///
- /// The configuration required to set up a new instance of .
- /// The same for chained calls.
- public static IServiceCollection WithCommandManager(this IServiceCollection collection, Action action = null)
- where T : CommandManager
- {
- var context = new CommandBuildingConfiguration();
-
- action?.Invoke(context);
-
- collection.AddCommandManager(context);
-
- return collection;
- }
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- public static IServiceCollection AddCommandManager(this IServiceCollection collection, CommandBuildingConfiguration configuration)
- where T : CommandManager
- {
- collection.AddComponents(configuration);
-
- collection.AddSingleton(configuration);
-
- collection.TryAddSingleton();
-
- return collection;
- }
- }
-}
diff --git a/src/CSF.Core/Extensions/Internal/LinqHelper.cs b/src/CSF.Core/Helpers/Internal/LinqHelpers.cs
similarity index 97%
rename from src/CSF.Core/Extensions/Internal/LinqHelper.cs
rename to src/CSF.Core/Helpers/Internal/LinqHelpers.cs
index 48529ad..b692268 100644
--- a/src/CSF.Core/Extensions/Internal/LinqHelper.cs
+++ b/src/CSF.Core/Helpers/Internal/LinqHelpers.cs
@@ -2,7 +2,7 @@
namespace CSF
{
- internal static class LinqHelper
+ internal static class LinqHelpers
{
public static IEnumerable CastWhere(this IEnumerable input)
{
diff --git a/src/CSF.Core/Extensions/Internal/BuildHelper.cs b/src/CSF.Core/Helpers/Internal/ReflectionHelpers.cs
similarity index 51%
rename from src/CSF.Core/Extensions/Internal/BuildHelper.cs
rename to src/CSF.Core/Helpers/Internal/ReflectionHelpers.cs
index 7b26ac6..eac4ced 100644
--- a/src/CSF.Core/Extensions/Internal/BuildHelper.cs
+++ b/src/CSF.Core/Helpers/Internal/ReflectionHelpers.cs
@@ -2,79 +2,65 @@
namespace CSF
{
- internal static class BuildHelper
+ internal static class ReflectionHelpers
{
- public static HashSet Build(this FrameworkBuilder context)
- {
- var modules = context.BuildModules();
-
- return modules.SelectMany(x => x.Components).ToHashSet();
- }
-
- public static IEnumerable BuildModules(this FrameworkBuilder context)
- {
- var typeReaders = TypeReader.CreateDefaultReaders().UnionBy(context.TypeReaders, x => x.Type).ToDictionary(x => x.Type, x => x);
-
- var rootReader = typeof(TypeReader);
- foreach (var assembly in context.Assemblies)
- foreach (var type in assembly.GetTypes())
- if (rootReader.IsAssignableFrom(type) && !type.IsAbstract && !type.ContainsGenericParameters)
- {
- var reader = Activator.CreateInstance(type) as TypeReader;
-
- // replace existing typereader with replacement handler
- if (typeReaders.ContainsKey(reader.Type))
- typeReaders[reader.Type] = reader;
- else
- typeReaders.Add(reader.Type, reader);
- }
-
- var rootType = typeof(ModuleBase);
- foreach (var assembly in context.Assemblies)
- foreach (var type in assembly.GetTypes())
- if (rootType.IsAssignableFrom(type) && !type.IsAbstract && !type.ContainsGenericParameters)
- yield return new Module(type, typeReaders);
- }
-
- public static IConditionalComponent[] Build(this Module module, IDictionary typeReaders)
- {
- var commands = (IEnumerable)module.BuildCommands(typeReaders)
- .OrderBy(x => x.Parameters.Length);
-
- var modules = (IEnumerable)module.BuildModules(typeReaders)
- .OrderBy(x => x.Components.Length);
-
- return commands.Concat(modules)
- .ToArray();
- }
-
- public static IEnumerable BuildModules(this Module module, IDictionary typeReaders)
+ private static IEnumerable GetModules(Module module, IDictionary typeReaders)
{
foreach (var group in module.Type.GetNestedTypes())
+ {
foreach (var attribute in group.GetCustomAttributes(true))
+ {
if (attribute is GroupAttribute gattribute)
+ {
yield return new Module(group, typeReaders, module, gattribute.Name, gattribute.Aliases);
+ }
+ }
+ }
}
- public static IEnumerable BuildCommands(this Module module, IDictionary typeReaders)
+ private static IEnumerable GetCommands(Module module, IDictionary typeReaders)
{
foreach (var method in module.Type.GetMethods())
{
var attributes = method.GetCustomAttributes(true);
- string[] aliases = Array.Empty();
+ string[] aliases = [];
foreach (var attribute in attributes)
- if (attribute is CommandAttribute commandAttribute)
- aliases = commandAttribute.Aliases;
+ {
+ if (attribute is CommandAttribute cmd)
+ {
+ aliases = cmd.Aliases;
+ }
+
+ // if set to noReg, dont build the command.
+ if (attribute is DontRegisterAttribute noReg)
+ {
+ continue;
+ }
+ }
- if (!aliases.Any())
+ if (aliases.Length == 0)
+ {
continue;
+ }
yield return new Command(module, method, aliases, typeReaders);
}
}
- public static IParameterComponent[] BuildParameters(this MethodBase method, IDictionary typeReaders)
+ public static IConditionalComponent[] GetComponents(this Module module, IDictionary typeReaders)
+ {
+ var commands = (IEnumerable)GetCommands(module, typeReaders)
+ .OrderBy(x => x.Parameters.Length);
+
+ var modules = (IEnumerable)GetModules(module, typeReaders)
+ .OrderBy(x => x.Components.Length);
+
+ return commands.Concat(modules)
+ .ToArray();
+ }
+
+ public static IParameterComponent[] GetParameters(this MethodBase method, IDictionary typeReaders)
{
var parameters = method.GetParameters();
@@ -82,9 +68,13 @@ public static IParameterComponent[] BuildParameters(this MethodBase method, IDic
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].GetCustomAttributes().Any(x => x is ComplexAttribute))
+ {
arr[i] = new ComplexParameter(parameters[i], typeReaders);
+ }
else
+ {
arr[i] = new Parameter(parameters[i], typeReaders);
+ }
}
return arr;
diff --git a/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs b/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
new file mode 100644
index 0000000..ec35eaa
--- /dev/null
+++ b/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
@@ -0,0 +1,21 @@
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace CSF
+{
+ internal static class ThrowHelpers
+ {
+ [DoesNotReturn]
+ public static void InvalidOp([DisallowNull] string failureMessage)
+ {
+ throw new InvalidOperationException(failureMessage);
+ }
+
+ [DoesNotReturn]
+ public static void InvalidArg(object value, [CallerArgumentExpression(nameof(value))] string arg = null)
+ {
+ throw new ArgumentException("Argument is not in valid state, being null, empty or whitespace.", paramName: arg);
+ }
+ }
+}
diff --git a/src/CSF.Core/Helpers/ServiceHelper.cs b/src/CSF.Core/Helpers/ServiceHelper.cs
new file mode 100644
index 0000000..465577a
--- /dev/null
+++ b/src/CSF.Core/Helpers/ServiceHelper.cs
@@ -0,0 +1,51 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System.ComponentModel;
+
+namespace CSF
+{
+ public static class ServiceHelper
+ {
+ ///
+ /// Includes inheriting into the this method is called on.
+ ///
+ /// The type inheriting to include in the collection.
+ ///
+ /// The configuration required to set up a new instance of .
+ /// The same for chained calls.
+ public static IServiceCollection WithCommandManager(this IServiceCollection collection, Action action = null)
+ {
+ var context = new CMBuilder();
+
+ action?.Invoke(context);
+
+ collection.AddCommandManager(context);
+
+ return collection;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static IServiceCollection AddCommandManager(this IServiceCollection collection, CMBuilder configuration)
+ {
+ ModulesAddTransient(collection, configuration);
+
+ var implementor = configuration.Build();
+
+ collection.TryAddSingleton(implementor);
+
+ return collection;
+ }
+
+ private static IServiceCollection ModulesAddTransient(IServiceCollection collection, CMBuilder configuration)
+ {
+ var rootType = typeof(ModuleBase);
+
+ foreach (var assembly in configuration.Assemblies)
+ foreach (var type in assembly.GetTypes())
+ if (rootType.IsAssignableFrom(type) && !type.IsAbstract && !type.ContainsGenericParameters)
+ collection.TryAddTransient(type);
+
+ return collection;
+ }
+ }
+}
diff --git a/src/CSF.Core/TypeReaders/BaseTypeReader.cs b/src/CSF.Core/TypeReaders/BaseTypeReader.cs
index fa3fc4e..410ef7f 100644
--- a/src/CSF.Core/TypeReaders/BaseTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/BaseTypeReader.cs
@@ -6,14 +6,14 @@ internal class BaseTypeReader : TypeReader
private readonly static Lazy> _container = new(ValueGenerator);
- public override Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
{
var parser = _container.Value[Type] as Tpd;
if (parser(value, out var result))
- return Success(result);
+ return ValueTask.FromResult(Success(result));
- return Failure($"The provided value does not match the expected type. Expected {typeof(T).Name}, got {value}. At: '{parameter.Name}'");
+ return ValueTask.FromResult(Error($"The provided value does not match the expected type. Expected {typeof(T).Name}, got {value}. At: '{parameter.Name}'"));
}
private static IReadOnlyDictionary ValueGenerator()
diff --git a/src/CSF.Core/TypeReaders/ColorTypeReader.cs b/src/CSF.Core/TypeReaders/ColorTypeReader.cs
index 4b12cf4..7c09f20 100644
--- a/src/CSF.Core/TypeReaders/ColorTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/ColorTypeReader.cs
@@ -45,19 +45,19 @@ public ColorTypeReader()
_spacedColors = spacedNames;
}
- public override Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
{
if (int.TryParse(value.Replace("#", "").Replace("0x", ""), NumberStyles.HexNumber, null, out var hexNumber))
- return Success(Color.FromArgb(hexNumber));
+ return ValueTask.FromResult(Success(Color.FromArgb(hexNumber)));
var name = value;
_spacedColors.TryGetValue(name, out name);
if (_colors.TryGetValue(name, out var color))
- return Success(color);
+ return ValueTask.FromResult(Success(color));
- return Failure($"The provided value is not a color. Got: '{value}'. At: '{parameter.Name}'");
+ return ValueTask.FromResult(Error($"The provided value is not a color. Got: '{value}'. At: '{parameter.Name}'"));
}
}
}
diff --git a/src/CSF.Core/TypeReaders/EnumTypeReader.cs b/src/CSF.Core/TypeReaders/EnumTypeReader.cs
index e192fc2..698fc31 100644
--- a/src/CSF.Core/TypeReaders/EnumTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/EnumTypeReader.cs
@@ -1,23 +1,15 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace CSF
+namespace CSF
{
- ///
- /// Defines the default for enums.
- ///
- ///
- /// To implement this typereader, you must first define it with the associated enum and add it to the .
- ///
- /// The enum this parser belongs to.
- public class EnumTypeReader : TypeReader
- where T : struct, Enum
+ internal class EnumTypeReader(Type targetEnumType) : TypeReader
{
- public override Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value)
+ public override Type Type { get; } = targetEnumType;
+
+ public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
{
- if (Enum.TryParse(value, true, out var result))
- return Success(result);
+ if (Enum.TryParse(Type, value, true, out var result))
+ return ValueTask.FromResult(Success(result));
- return Failure($"The provided value is not a part the enum specified. Expected: '{typeof(T).Name}', got: '{value}'. At: '{parameter.Name}'");
+ return ValueTask.FromResult(Error($"The provided value is not a part the enum specified. Expected: '{Type.Name}', got: '{value}'. At: '{parameter.Name}'"));
}
}
}
diff --git a/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs b/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs
index d97e683..9d4efdc 100644
--- a/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs
@@ -33,7 +33,7 @@ public TimeSpanTypeReader()
};
}
- public override Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
{
if (!TimeSpan.TryParse(value, out TimeSpan span))
{
@@ -48,10 +48,10 @@ public override Result Evaluate(ICommandContext context, IParameterComponent par
#pragma warning restore IDE0220 // Add explicit cast
}
else
- return Failure($"The provided value is no timespan. Got: '{value}'. At: '{parameter.Name}'");
+ return ValueTask.FromResult(Error($"The provided value is no timespan. Got: '{value}'. At: '{parameter.Name}'"));
}
- return Success(span);
+ return ValueTask.FromResult(Success(span));
}
private static TimeSpan Seconds(string match)
diff --git a/src/CSF.Hosting/CSF.Hosting.csproj b/src/CSF.Hosting/CSF.Hosting.csproj
index 6ceddc3..7cb0313 100644
--- a/src/CSF.Hosting/CSF.Hosting.csproj
+++ b/src/CSF.Hosting/CSF.Hosting.csproj
@@ -46,7 +46,7 @@
-
+
diff --git a/src/CSF.Tests.Hosting/CSF.Tests.Hosting.csproj b/src/CSF.Tests.Hosting/CSF.Tests.Hosting.csproj
index e3f1fc0..c686f43 100644
--- a/src/CSF.Tests.Hosting/CSF.Tests.Hosting.csproj
+++ b/src/CSF.Tests.Hosting/CSF.Tests.Hosting.csproj
@@ -8,7 +8,7 @@
-
+
From 72578431a7d30c81dc691e1298372d8949a2eaa2 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Thu, 25 Jan 2024 18:40:30 +0100
Subject: [PATCH 04/40] Improve typereader gen, value duplicate checks,
throwhelpers
---
src/CSF.Core/Abstractions/ModuleBase.cs | 12 ++++++------
.../Abstractions/PreconditionAttribute.cs | 4 ++--
src/CSF.Core/Abstractions/Results/IResult.cs | 3 +++
src/CSF.Core/Abstractions/TypeReader.cs | 4 ++--
src/CSF.Core/Attributes/CommandAttribute.cs | 17 ++++++++++++++---
.../Attributes/DescriptionAttribute.cs | 2 +-
src/CSF.Core/Attributes/GroupAttribute.cs | 19 +++++++++++++++----
src/CSF.Core/Attributes/PriorityAttribute.cs | 2 +-
src/CSF.Core/CSF.Core.csproj | 1 +
src/CSF.Core/CommandManager.cs | 2 +-
src/CSF.Core/Components/Parameter.cs | 2 +-
src/CSF.Core/Contexts/CommandContext.cs | 12 ++++++++----
src/CSF.Core/Exceptions/CheckException.cs | 8 ++------
src/CSF.Core/Exceptions/CommandException.cs | 8 ++------
.../Internal/ArgumentMissingException.cs | 14 ++++++++++++++
.../Internal/RangeDuplicateException.cs | 14 ++++++++++++++
src/CSF.Core/Exceptions/MatchException.cs | 7 ++-----
src/CSF.Core/Exceptions/ReadException.cs | 8 ++------
src/CSF.Core/Exceptions/SearchException.cs | 8 ++------
src/CSF.Core/Helpers/Internal/ThrowHelpers.cs | 13 ++++++++++---
src/CSF.Core/TypeReaders/EnumTypeReader.cs | 12 ++++++++++++
.../TypeReaders/TimeSpanTypeReader.cs | 7 +++++--
22 files changed, 120 insertions(+), 59 deletions(-)
create mode 100644 src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs
create mode 100644 src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs
diff --git a/src/CSF.Core/Abstractions/ModuleBase.cs b/src/CSF.Core/Abstractions/ModuleBase.cs
index 35066c7..09df4bb 100644
--- a/src/CSF.Core/Abstractions/ModuleBase.cs
+++ b/src/CSF.Core/Abstractions/ModuleBase.cs
@@ -50,7 +50,7 @@ public IServiceProvider Services
public virtual void Respond(string message)
=> Console.WriteLine(message);
- public virtual RunResult ReturnTypeHandle(object value)
+ public virtual RunResult ReturnTypeResolve(object value)
{
switch (value)
{
@@ -59,18 +59,18 @@ public virtual RunResult ReturnTypeHandle(object value)
case null:
return new(Command, null);
default:
- throw new NotSupportedException("The return value of this command is not supported.");
+ throw new NotSupportedException($"The return value of the command in question is not supported. Consider overriding {nameof(ReturnTypeResolve)} to add your own return type resolver.");
}
}
- public virtual async ValueTask BeforeExecuteAsync()
+ public virtual ValueTask BeforeExecuteAsync()
{
-
+ return ValueTask.CompletedTask;
}
- public virtual async ValueTask AfterExecuteAsync()
+ public virtual ValueTask AfterExecuteAsync()
{
-
+ return ValueTask.CompletedTask;
}
}
}
diff --git a/src/CSF.Core/Abstractions/PreconditionAttribute.cs b/src/CSF.Core/Abstractions/PreconditionAttribute.cs
index db05341..61e71bd 100644
--- a/src/CSF.Core/Abstractions/PreconditionAttribute.cs
+++ b/src/CSF.Core/Abstractions/PreconditionAttribute.cs
@@ -21,7 +21,7 @@ public abstract class PreconditionAttribute : Attribute
public static CheckResult Error([DisallowNull] Exception exception)
{
if (exception == null)
- ThrowHelpers.InvalidArg(exception);
+ ThrowHelpers.ArgMissing(exception);
if (exception is CheckException checkEx)
{
@@ -33,7 +33,7 @@ public static CheckResult Error([DisallowNull] Exception exception)
public virtual CheckResult Error([DisallowNull] string error)
{
if (string.IsNullOrEmpty(error))
- ThrowHelpers.InvalidArg(error);
+ ThrowHelpers.ArgMissing(error);
return new(new CheckException(error));
}
diff --git a/src/CSF.Core/Abstractions/Results/IResult.cs b/src/CSF.Core/Abstractions/Results/IResult.cs
index 6135ab6..3d64251 100644
--- a/src/CSF.Core/Abstractions/Results/IResult.cs
+++ b/src/CSF.Core/Abstractions/Results/IResult.cs
@@ -8,5 +8,8 @@ namespace CSF
{
public interface IResult
{
+ public Exception Exception { get; }
+
+ public bool Success { get; }
}
}
diff --git a/src/CSF.Core/Abstractions/TypeReader.cs b/src/CSF.Core/Abstractions/TypeReader.cs
index 8811e0d..8b7cf28 100644
--- a/src/CSF.Core/Abstractions/TypeReader.cs
+++ b/src/CSF.Core/Abstractions/TypeReader.cs
@@ -48,7 +48,7 @@ internal ValueTask ObjectEvaluateAsync(ICommandContext context, IPar
public virtual ReadResult Error([DisallowNull] Exception exception)
{
if (exception == null)
- ThrowHelpers.InvalidArg(exception);
+ ThrowHelpers.ArgMissing(exception);
if (exception is ReadException readEx)
{
@@ -60,7 +60,7 @@ public virtual ReadResult Error([DisallowNull] Exception exception)
public virtual ReadResult Error([DisallowNull] string error)
{
if (string.IsNullOrEmpty(error))
- ThrowHelpers.InvalidArg(error);
+ ThrowHelpers.ArgMissing(error);
return new(new ReadException(error));
}
diff --git a/src/CSF.Core/Attributes/CommandAttribute.cs b/src/CSF.Core/Attributes/CommandAttribute.cs
index 0764f11..fc01fc2 100644
--- a/src/CSF.Core/Attributes/CommandAttribute.cs
+++ b/src/CSF.Core/Attributes/CommandAttribute.cs
@@ -36,13 +36,24 @@ public CommandAttribute([DisallowNull] string name)
public CommandAttribute([DisallowNull] string name, params string[] aliases)
{
if (string.IsNullOrWhiteSpace(name))
- ThrowHelpers.InvalidArg(name);
+ ThrowHelpers.ArgMissing(name);
var arr = new string[aliases.Length + 1];
+ for (int i = 0; i < aliases.Length; i++)
+ {
+ if (string.IsNullOrWhiteSpace(aliases[i]))
+ ThrowHelpers.ArgMissing(aliases);
- arr[0] = name;
+ if (arr.Contains(aliases[i]))
+ ThrowHelpers.RangeDuplicate(aliases);
+
+ arr[i + 1] = aliases[i];
+ }
- Array.Copy(aliases, 0, arr, 1, aliases.Length);
+ if (arr.Contains(name))
+ ThrowHelpers.RangeDuplicate(aliases);
+
+ arr[0] = name;
Name = name;
Aliases = arr;
diff --git a/src/CSF.Core/Attributes/DescriptionAttribute.cs b/src/CSF.Core/Attributes/DescriptionAttribute.cs
index 0def0ec..a183501 100644
--- a/src/CSF.Core/Attributes/DescriptionAttribute.cs
+++ b/src/CSF.Core/Attributes/DescriptionAttribute.cs
@@ -20,7 +20,7 @@ public sealed class DescriptionAttribute : Attribute
public DescriptionAttribute([DisallowNull] string description)
{
if (string.IsNullOrWhiteSpace(description))
- ThrowHelpers.InvalidArg(description);
+ ThrowHelpers.ArgMissing(description);
Description = description;
}
diff --git a/src/CSF.Core/Attributes/GroupAttribute.cs b/src/CSF.Core/Attributes/GroupAttribute.cs
index 525e4b2..f9a15ca 100644
--- a/src/CSF.Core/Attributes/GroupAttribute.cs
+++ b/src/CSF.Core/Attributes/GroupAttribute.cs
@@ -22,7 +22,7 @@ public sealed class GroupAttribute : Attribute
/// Creates a new with defined name.
///
///
- public GroupAttribute(string name)
+ public GroupAttribute([DisallowNull] string name)
: this(name, [])
{
@@ -37,13 +37,24 @@ public GroupAttribute(string name)
public GroupAttribute([DisallowNull] string name, params string[] aliases)
{
if (string.IsNullOrWhiteSpace(name))
- ThrowHelpers.InvalidArg(name);
+ ThrowHelpers.ArgMissing(name);
var arr = new string[aliases.Length + 1];
+ for (int i = 0; i < aliases.Length; i++)
+ {
+ if (string.IsNullOrWhiteSpace(aliases[i]))
+ ThrowHelpers.ArgMissing(aliases);
- arr[0] = name;
+ if (arr.Contains(aliases[i]))
+ ThrowHelpers.RangeDuplicate(aliases);
+
+ arr[i + 1] = aliases[i];
+ }
- Array.Copy(aliases, 0, arr, 1, aliases.Length);
+ if (arr.Contains(name))
+ ThrowHelpers.RangeDuplicate(aliases);
+
+ arr[0] = name;
Name = name;
Aliases = arr;
diff --git a/src/CSF.Core/Attributes/PriorityAttribute.cs b/src/CSF.Core/Attributes/PriorityAttribute.cs
index 210853c..b33d60b 100644
--- a/src/CSF.Core/Attributes/PriorityAttribute.cs
+++ b/src/CSF.Core/Attributes/PriorityAttribute.cs
@@ -12,7 +12,7 @@ namespace CSF
/// Creates a new with provided priority.
///
/// The priority of this command, which can be between 0 and 255.
- [AttributeUsage(AttributeTargets.Method)]
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class PriorityAttribute([DisallowNull] byte priority) : Attribute
{
///
diff --git a/src/CSF.Core/CSF.Core.csproj b/src/CSF.Core/CSF.Core.csproj
index c7b9756..c8dece9 100644
--- a/src/CSF.Core/CSF.Core.csproj
+++ b/src/CSF.Core/CSF.Core.csproj
@@ -4,6 +4,7 @@
net8.0
enable
CSF
+ true
2.1
2.1
diff --git a/src/CSF.Core/CommandManager.cs b/src/CSF.Core/CommandManager.cs
index 0e46f04..ed842c3 100644
--- a/src/CSF.Core/CommandManager.cs
+++ b/src/CSF.Core/CommandManager.cs
@@ -157,7 +157,7 @@ internal static async ValueTask RunAsync(ICommandContext context, Mat
await module.AfterExecuteAsync();
- return module.ReturnTypeHandle(value);
+ return module.ReturnTypeResolve(value);
}
catch (Exception exception)
{
diff --git a/src/CSF.Core/Components/Parameter.cs b/src/CSF.Core/Components/Parameter.cs
index d0851d6..0312fee 100644
--- a/src/CSF.Core/Components/Parameter.cs
+++ b/src/CSF.Core/Components/Parameter.cs
@@ -58,7 +58,7 @@ internal Parameter(ParameterInfo parameterInfo, IDictionary ty
IsRemainder = false;
if (Type.IsEnum)
- TypeReader = new EnumTypeReader(Type);
+ TypeReader = EnumTypeReader.GetOrCreate(Type);
else if (Type != typeof(string) && Type != typeof(object))
TypeReader = typeReaders[Type];
diff --git a/src/CSF.Core/Contexts/CommandContext.cs b/src/CSF.Core/Contexts/CommandContext.cs
index 547851d..adc03fe 100644
--- a/src/CSF.Core/Contexts/CommandContext.cs
+++ b/src/CSF.Core/Contexts/CommandContext.cs
@@ -1,10 +1,14 @@
namespace CSF
{
- ///
- /// Represents a class that's used to describe data from the command.
- ///
- public class CommandContext(T options) : ICommandContext
+ public class CommandContext(T options) : CommandContext(options), ICommandContext
where T : IExecutionOptions
+ {
+ public new T Options { get; } = options;
+
+ IExecutionOptions ICommandContext.Options { get; } = options;
+ }
+
+ public class CommandContext(IExecutionOptions options) : ICommandContext
{
public IExecutionOptions Options { get; } = options;
}
diff --git a/src/CSF.Core/Exceptions/CheckException.cs b/src/CSF.Core/Exceptions/CheckException.cs
index 13e8269..6f7d1ac 100644
--- a/src/CSF.Core/Exceptions/CheckException.cs
+++ b/src/CSF.Core/Exceptions/CheckException.cs
@@ -3,12 +3,8 @@
///
/// Represents a that is thrown when no matched command succeeded its precondition checks.
///
- public sealed class CheckException : ExecutionException
+ public sealed class CheckException(string message, Exception innerException = null)
+ : ExecutionException(message, innerException)
{
- public CheckException(string message, Exception innerException = null)
- : base(message, innerException)
- {
-
- }
}
}
diff --git a/src/CSF.Core/Exceptions/CommandException.cs b/src/CSF.Core/Exceptions/CommandException.cs
index 125404b..bf72715 100644
--- a/src/CSF.Core/Exceptions/CommandException.cs
+++ b/src/CSF.Core/Exceptions/CommandException.cs
@@ -3,12 +3,8 @@
///
/// Represents a that is thrown when the command being executed failed to run its body.
///
- public sealed class CommandException : ExecutionException
+ public sealed class CommandException(string message, Exception innerException = null)
+ : ExecutionException(message, innerException)
{
- public CommandException(string message, Exception innerException = null)
- : base(message, innerException)
- {
-
- }
}
}
diff --git a/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs b/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs
new file mode 100644
index 0000000..61b4403
--- /dev/null
+++ b/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF
+{
+ internal sealed class ArgumentMissingException(string paramName, string message)
+ : ArgumentException(message, paramName)
+ {
+
+ }
+}
diff --git a/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs b/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs
new file mode 100644
index 0000000..5f75b2b
--- /dev/null
+++ b/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF
+{
+ internal sealed class RangeDuplicateException(string paramName, string message)
+ : ArgumentException(paramName, message)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/CSF.Core/Exceptions/MatchException.cs b/src/CSF.Core/Exceptions/MatchException.cs
index cf6b1d0..c5df10b 100644
--- a/src/CSF.Core/Exceptions/MatchException.cs
+++ b/src/CSF.Core/Exceptions/MatchException.cs
@@ -1,11 +1,8 @@
namespace CSF
{
- public class MatchException : ExecutionException
+ public class MatchException(string message, Exception innerException = null)
+ : ExecutionException(message, innerException)
{
- public MatchException(string message, Exception innerException = null)
- : base(message, innerException)
- {
- }
}
}
diff --git a/src/CSF.Core/Exceptions/ReadException.cs b/src/CSF.Core/Exceptions/ReadException.cs
index 2406df5..543ca25 100644
--- a/src/CSF.Core/Exceptions/ReadException.cs
+++ b/src/CSF.Core/Exceptions/ReadException.cs
@@ -3,12 +3,8 @@
///
/// Represents a that is thrown when no matched command succeeded parsing its parameters.
///
- public sealed class ReadException : ExecutionException
+ public sealed class ReadException(string message, Exception innerException = null)
+ : ExecutionException(message, innerException)
{
- public ReadException(string message, Exception innerException = null)
- : base(message, innerException)
- {
-
- }
}
}
diff --git a/src/CSF.Core/Exceptions/SearchException.cs b/src/CSF.Core/Exceptions/SearchException.cs
index ec01158..7f3726a 100644
--- a/src/CSF.Core/Exceptions/SearchException.cs
+++ b/src/CSF.Core/Exceptions/SearchException.cs
@@ -3,12 +3,8 @@
///
/// Represents a that is thrown when no command could be found.
///
- public sealed class SearchException : ExecutionException
+ public sealed class SearchException(string message, Exception innerException = null)
+ : ExecutionException(message, innerException)
{
- public SearchException(string message, Exception innerException = null)
- : base(message, innerException)
- {
-
- }
}
}
diff --git a/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs b/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
index ec35eaa..5ad3bfa 100644
--- a/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
+++ b/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
@@ -1,4 +1,5 @@
-using System.Collections;
+using CSF.Exceptions;
+using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
@@ -13,9 +14,15 @@ public static void InvalidOp([DisallowNull] string failureMessage)
}
[DoesNotReturn]
- public static void InvalidArg(object value, [CallerArgumentExpression(nameof(value))] string arg = null)
+ public static void ArgMissing(object value, [CallerArgumentExpression(nameof(value))] string arg = null)
{
- throw new ArgumentException("Argument is not in valid state, being null, empty or whitespace.", paramName: arg);
+ throw new ArgumentMissingException(arg, "Argument is not in valid state, being null, empty or whitespace.");
+ }
+
+ [DoesNotReturn]
+ public static void RangeDuplicate(object value, [CallerArgumentExpression(nameof(value))] string arg = null)
+ {
+ throw new RangeDuplicateException(arg, "Range contains a duplicate value, which is not supported by the implementation.");
}
}
}
diff --git a/src/CSF.Core/TypeReaders/EnumTypeReader.cs b/src/CSF.Core/TypeReaders/EnumTypeReader.cs
index 698fc31..161bf35 100644
--- a/src/CSF.Core/TypeReaders/EnumTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/EnumTypeReader.cs
@@ -2,6 +2,8 @@
{
internal class EnumTypeReader(Type targetEnumType) : TypeReader
{
+ private static readonly Dictionary _readers = [];
+
public override Type Type { get; } = targetEnumType;
public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
@@ -11,5 +13,15 @@ public override ValueTask EvaluateAsync(ICommandContext context, IPa
return ValueTask.FromResult(Error($"The provided value is not a part the enum specified. Expected: '{Type.Name}', got: '{value}'. At: '{parameter.Name}'"));
}
+
+ internal static EnumTypeReader GetOrCreate(Type type)
+ {
+ if (_readers.TryGetValue(type, out var reader))
+ return reader;
+
+ _readers.Add(type, reader = new EnumTypeReader(type));
+
+ return reader;
+ }
}
}
diff --git a/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs b/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs
index 9d4efdc..4a018f5 100644
--- a/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs
@@ -2,10 +2,10 @@
namespace CSF
{
- internal class TimeSpanTypeReader : TypeReader
+ internal partial class TimeSpanTypeReader : TypeReader
{
private readonly IReadOnlyDictionary> _callback;
- private readonly Regex _regex = new(@"(\d*)\s*([a-zA-Z]*)\s*(?:and|,)?\s*", RegexOptions.Compiled);
+ private readonly Regex _regex = GenTSRegex();
public TimeSpanTypeReader()
{
@@ -71,5 +71,8 @@ private static TimeSpan Weeks(string match)
private static TimeSpan Months(string match)
=> new(((int)(int.Parse(match) * 30.437)), 0, 0, 0);
+
+ [GeneratedRegex(@"(\d*)\s*([a-zA-Z]*)\s*(?:and|,)?\s*", RegexOptions.Compiled)]
+ private static partial Regex GenTSRegex();
}
}
From f0fa0bd8c095f266b45204f3d1031a6e331d792b Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Thu, 25 Jan 2024 18:40:45 +0100
Subject: [PATCH 05/40] Simple cleanup
---
src/CSF.Core/Abstractions/Results/IResult.cs | 12 +++---------
src/CSF.Core/CMBuilder.cs | 2 +-
src/CSF.Core/CommandManager.cs | 2 +-
src/CSF.Core/CommandManagerHelper.cs | 2 +-
src/CSF.Core/Components/Command.cs | 3 +--
src/CSF.Core/Components/Module.cs | 2 +-
src/CSF.Core/Exceptions/CheckException.cs | 2 +-
src/CSF.Core/Exceptions/CommandException.cs | 2 +-
.../Exceptions/Internal/ArgumentMissingException.cs | 10 ++--------
.../Exceptions/Internal/RangeDuplicateException.cs | 10 ++--------
src/CSF.Core/Exceptions/MatchException.cs | 2 +-
src/CSF.Core/Exceptions/ReadException.cs | 2 +-
src/CSF.Core/Exceptions/SearchException.cs | 2 +-
src/CSF.Core/Helpers/Internal/ThrowHelpers.cs | 4 +---
src/CSF.Core/TypeReaders/EnumTypeReader.cs | 2 +-
src/CSF.Tests.Console/Modules/AsyncModule.cs | 8 +-------
16 files changed, 20 insertions(+), 47 deletions(-)
diff --git a/src/CSF.Core/Abstractions/Results/IResult.cs b/src/CSF.Core/Abstractions/Results/IResult.cs
index 3d64251..5cda7ce 100644
--- a/src/CSF.Core/Abstractions/Results/IResult.cs
+++ b/src/CSF.Core/Abstractions/Results/IResult.cs
@@ -1,15 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF
+namespace CSF
{
public interface IResult
{
- public Exception Exception { get; }
+ public Exception Exception { get; }
- public bool Success { get; }
+ public bool Success { get; }
}
}
diff --git a/src/CSF.Core/CMBuilder.cs b/src/CSF.Core/CMBuilder.cs
index 625e07b..c9a9547 100644
--- a/src/CSF.Core/CMBuilder.cs
+++ b/src/CSF.Core/CMBuilder.cs
@@ -6,7 +6,7 @@ public sealed class CMBuilder
{
private bool _disposed = false;
- public HashSet Assemblies { get; set; } = [ Assembly.GetEntryAssembly() ];
+ public HashSet Assemblies { get; set; } = [Assembly.GetEntryAssembly()];
public HashSet TypeReaders { get; set; } = [];
diff --git a/src/CSF.Core/CommandManager.cs b/src/CSF.Core/CommandManager.cs
index ed842c3..256d13b 100644
--- a/src/CSF.Core/CommandManager.cs
+++ b/src/CSF.Core/CommandManager.cs
@@ -41,7 +41,7 @@ public async ValueTask ExecuteAsync(ICommandContext context, params obj
// define a fallback for unsuccesful execution.
MatchResult? fallback = default;
-
+
// order searches by descending for priority definitions.
foreach (var search in searches.OrderByDescending(x => x.Command.Priority))
{
diff --git a/src/CSF.Core/CommandManagerHelper.cs b/src/CSF.Core/CommandManagerHelper.cs
index 250a93b..6bd1f61 100644
--- a/src/CSF.Core/CommandManagerHelper.cs
+++ b/src/CSF.Core/CommandManagerHelper.cs
@@ -17,7 +17,7 @@ public static IEnumerable RecursiveSearch(this IEnumerable typeReaders, Module roo
Components = this.GetComponents(typeReaders);
Name = expectedName ?? type.Name;
- Aliases = aliases ?? [ Name ];
+ Aliases = aliases ?? [Name];
}
///
diff --git a/src/CSF.Core/Exceptions/CheckException.cs b/src/CSF.Core/Exceptions/CheckException.cs
index 6f7d1ac..8b97621 100644
--- a/src/CSF.Core/Exceptions/CheckException.cs
+++ b/src/CSF.Core/Exceptions/CheckException.cs
@@ -3,7 +3,7 @@
///
/// Represents a that is thrown when no matched command succeeded its precondition checks.
///
- public sealed class CheckException(string message, Exception innerException = null)
+ public sealed class CheckException(string message, Exception innerException = null)
: ExecutionException(message, innerException)
{
}
diff --git a/src/CSF.Core/Exceptions/CommandException.cs b/src/CSF.Core/Exceptions/CommandException.cs
index bf72715..8ae1c6e 100644
--- a/src/CSF.Core/Exceptions/CommandException.cs
+++ b/src/CSF.Core/Exceptions/CommandException.cs
@@ -3,7 +3,7 @@
///
/// Represents a that is thrown when the command being executed failed to run its body.
///
- public sealed class CommandException(string message, Exception innerException = null)
+ public sealed class CommandException(string message, Exception innerException = null)
: ExecutionException(message, innerException)
{
}
diff --git a/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs b/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs
index 61b4403..e57be6e 100644
--- a/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs
+++ b/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs
@@ -1,12 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF
+namespace CSF
{
- internal sealed class ArgumentMissingException(string paramName, string message)
+ internal sealed class ArgumentMissingException(string paramName, string message)
: ArgumentException(message, paramName)
{
diff --git a/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs b/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs
index 5f75b2b..1a799ab 100644
--- a/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs
+++ b/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs
@@ -1,12 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF
+namespace CSF
{
- internal sealed class RangeDuplicateException(string paramName, string message)
+ internal sealed class RangeDuplicateException(string paramName, string message)
: ArgumentException(paramName, message)
{
diff --git a/src/CSF.Core/Exceptions/MatchException.cs b/src/CSF.Core/Exceptions/MatchException.cs
index c5df10b..8fd43e6 100644
--- a/src/CSF.Core/Exceptions/MatchException.cs
+++ b/src/CSF.Core/Exceptions/MatchException.cs
@@ -1,6 +1,6 @@
namespace CSF
{
- public class MatchException(string message, Exception innerException = null)
+ public class MatchException(string message, Exception innerException = null)
: ExecutionException(message, innerException)
{
diff --git a/src/CSF.Core/Exceptions/ReadException.cs b/src/CSF.Core/Exceptions/ReadException.cs
index 543ca25..b0a3320 100644
--- a/src/CSF.Core/Exceptions/ReadException.cs
+++ b/src/CSF.Core/Exceptions/ReadException.cs
@@ -3,7 +3,7 @@
///
/// Represents a that is thrown when no matched command succeeded parsing its parameters.
///
- public sealed class ReadException(string message, Exception innerException = null)
+ public sealed class ReadException(string message, Exception innerException = null)
: ExecutionException(message, innerException)
{
}
diff --git a/src/CSF.Core/Exceptions/SearchException.cs b/src/CSF.Core/Exceptions/SearchException.cs
index 7f3726a..ac55443 100644
--- a/src/CSF.Core/Exceptions/SearchException.cs
+++ b/src/CSF.Core/Exceptions/SearchException.cs
@@ -3,7 +3,7 @@
///
/// Represents a that is thrown when no command could be found.
///
- public sealed class SearchException(string message, Exception innerException = null)
+ public sealed class SearchException(string message, Exception innerException = null)
: ExecutionException(message, innerException)
{
}
diff --git a/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs b/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
index 5ad3bfa..e039d61 100644
--- a/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
+++ b/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
@@ -1,6 +1,4 @@
-using CSF.Exceptions;
-using System.Collections;
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace CSF
diff --git a/src/CSF.Core/TypeReaders/EnumTypeReader.cs b/src/CSF.Core/TypeReaders/EnumTypeReader.cs
index 161bf35..8a881ab 100644
--- a/src/CSF.Core/TypeReaders/EnumTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/EnumTypeReader.cs
@@ -18,7 +18,7 @@ internal static EnumTypeReader GetOrCreate(Type type)
{
if (_readers.TryGetValue(type, out var reader))
return reader;
-
+
_readers.Add(type, reader = new EnumTypeReader(type));
return reader;
diff --git a/src/CSF.Tests.Console/Modules/AsyncModule.cs b/src/CSF.Tests.Console/Modules/AsyncModule.cs
index bc72969..c2b2915 100644
--- a/src/CSF.Tests.Console/Modules/AsyncModule.cs
+++ b/src/CSF.Tests.Console/Modules/AsyncModule.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF.Tests.Console.Modules
+namespace CSF.Tests.Console.Modules
{
public sealed class AsyncModule : ModuleBase
{
From 2b635570be583a5e053563b86682d7cb92efa7fe Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Thu, 25 Jan 2024 20:56:42 +0100
Subject: [PATCH 06/40] Attention to namespaces, naming convention system,
parsing start
---
.../Abstractions/Contexts/ICommandContext.cs | 13 ---
.../Attributes/DontRegisterAttribute.cs | 11 --
.../Attributes/RequireContextAttribute.cs | 26 -----
src/CSF.Core/CMBuilder.cs | 87 ---------------
src/CSF.Core/Components/CommandCell.cs | 59 ----------
src/CSF.Core/Components/Constructor.cs | 37 -------
src/CSF.Core/Contexts/CommandContext.cs | 15 ---
.../{ => Core}/Attributes/CommandAttribute.cs | 11 +-
.../{ => Core}/Attributes/ComplexAttribute.cs | 0
.../Attributes/DescriptionAttribute.cs | 3 +-
.../{ => Core}/Attributes/GroupAttribute.cs | 3 +-
.../Attributes/PrimaryConstructorAttribute.cs | 0
.../Attributes/PriorityAttribute.cs | 0
.../Attributes/RemainderAttribute.cs | 0
src/CSF.Core/Core/CommandConfiguration.cs | 12 +++
src/CSF.Core/{ => Core}/CommandManager.cs | 101 ++++++++++++------
.../Core/Execution/ICommandContext.cs | 8 ++
.../Core/Execution/Impl/CommandContext.cs | 7 ++
.../Execution}/ModuleBase.cs | 12 +--
.../{Abstractions => Core}/Results/IResult.cs | 0
.../Results/Impl}/CheckResult.cs | 0
.../Results/Impl}/MatchResult.cs | 10 +-
.../Results/Impl}/ReadResult.cs | 0
.../Results/Impl}/RunResult.cs | 10 +-
.../Results/Impl}/SearchResult.cs | 12 ++-
.../ArgumentMissingException.cs | 2 +-
src/CSF.Core/Exceptions/CheckException.cs | 2 +-
src/CSF.Core/Exceptions/CommandException.cs | 2 +-
.../ExecutionException.cs | 4 +-
src/CSF.Core/Exceptions/MatchException.cs | 2 +-
.../{Internal => }/RangeDuplicateException.cs | 2 +-
src/CSF.Core/Exceptions/ReadException.cs | 2 +-
src/CSF.Core/Exceptions/SearchException.cs | 2 +-
src/CSF.Core/ExecutionOptions.cs | 28 -----
.../LinqHelpers.cs => CollectionHelpers.cs} | 4 +-
.../ExecutionHelpers.cs} | 21 ++--
.../{Internal => }/ReflectionHelpers.cs | 41 ++++---
src/CSF.Core/Helpers/ServiceHelper.cs | 51 ---------
src/CSF.Core/Helpers/ServiceHelpers.cs | 30 ++++++
.../Helpers/{Internal => }/ThrowHelpers.cs | 5 +-
src/CSF.Core/Parsing/Impl/CharSpanParser.cs | 16 +++
src/CSF.Core/Parsing/Impl/StringParser.cs | 18 ++++
src/CSF.Core/Parsing/Parser.cs | 9 +-
src/CSF.Core/Parsing/ParserCell.cs | 31 ------
src/CSF.Core/Parsing/SpanParser.cs | 21 ++++
.../PreconditionAttribute.cs | 10 +-
.../IArgument.cs} | 6 +-
.../IArgumentBucket.cs} | 6 +-
.../IConditional.cs} | 6 +-
.../IComponent.cs => Reflection/INameable.cs} | 4 +-
.../Impl/ArgumentInfo.cs} | 10 +-
.../Impl/CommandInfo.cs} | 17 +--
.../Impl/ComplexArgumentInfo.cs} | 20 ++--
.../Impl/ModuleInfo.cs} | 14 ++-
.../TypeReaders/{ => Impl}/BaseTypeReader.cs | 6 +-
.../TypeReaders/{ => Impl}/ColorTypeReader.cs | 7 +-
.../TypeReaders/{ => Impl}/EnumTypeReader.cs | 6 +-
.../{ => Impl}/TimeSpanTypeReader.cs | 7 +-
.../TypeReader.cs | 30 ++----
.../TypeReaders/TimeOnlyTypeReader.cs | 10 --
60 files changed, 339 insertions(+), 550 deletions(-)
delete mode 100644 src/CSF.Core/Abstractions/Contexts/ICommandContext.cs
delete mode 100644 src/CSF.Core/Attributes/DontRegisterAttribute.cs
delete mode 100644 src/CSF.Core/Attributes/RequireContextAttribute.cs
delete mode 100644 src/CSF.Core/CMBuilder.cs
delete mode 100644 src/CSF.Core/Components/CommandCell.cs
delete mode 100644 src/CSF.Core/Components/Constructor.cs
delete mode 100644 src/CSF.Core/Contexts/CommandContext.cs
rename src/CSF.Core/{ => Core}/Attributes/CommandAttribute.cs (79%)
rename src/CSF.Core/{ => Core}/Attributes/ComplexAttribute.cs (100%)
rename src/CSF.Core/{ => Core}/Attributes/DescriptionAttribute.cs (93%)
rename src/CSF.Core/{ => Core}/Attributes/GroupAttribute.cs (96%)
rename src/CSF.Core/{ => Core}/Attributes/PrimaryConstructorAttribute.cs (100%)
rename src/CSF.Core/{ => Core}/Attributes/PriorityAttribute.cs (100%)
rename src/CSF.Core/{ => Core}/Attributes/RemainderAttribute.cs (100%)
create mode 100644 src/CSF.Core/Core/CommandConfiguration.cs
rename src/CSF.Core/{ => Core}/CommandManager.cs (61%)
create mode 100644 src/CSF.Core/Core/Execution/ICommandContext.cs
create mode 100644 src/CSF.Core/Core/Execution/Impl/CommandContext.cs
rename src/CSF.Core/{Abstractions => Core/Execution}/ModuleBase.cs (91%)
rename src/CSF.Core/{Abstractions => Core}/Results/IResult.cs (100%)
rename src/CSF.Core/{Results => Core/Results/Impl}/CheckResult.cs (100%)
rename src/CSF.Core/{Results => Core/Results/Impl}/MatchResult.cs (66%)
rename src/CSF.Core/{Results => Core/Results/Impl}/ReadResult.cs (100%)
rename src/CSF.Core/{Results => Core/Results/Impl}/RunResult.cs (66%)
rename src/CSF.Core/{Results => Core/Results/Impl}/SearchResult.cs (63%)
rename src/CSF.Core/Exceptions/{Internal => }/ArgumentMissingException.cs (84%)
rename src/CSF.Core/{Abstractions => Exceptions}/ExecutionException.cs (77%)
rename src/CSF.Core/Exceptions/{Internal => }/RangeDuplicateException.cs (84%)
delete mode 100644 src/CSF.Core/ExecutionOptions.cs
rename src/CSF.Core/Helpers/{Internal/LinqHelpers.cs => CollectionHelpers.cs} (94%)
rename src/CSF.Core/{CommandManagerHelper.cs => Helpers/ExecutionHelpers.cs} (80%)
rename src/CSF.Core/Helpers/{Internal => }/ReflectionHelpers.cs (60%)
delete mode 100644 src/CSF.Core/Helpers/ServiceHelper.cs
create mode 100644 src/CSF.Core/Helpers/ServiceHelpers.cs
rename src/CSF.Core/Helpers/{Internal => }/ThrowHelpers.cs (90%)
create mode 100644 src/CSF.Core/Parsing/Impl/CharSpanParser.cs
create mode 100644 src/CSF.Core/Parsing/Impl/StringParser.cs
delete mode 100644 src/CSF.Core/Parsing/ParserCell.cs
create mode 100644 src/CSF.Core/Parsing/SpanParser.cs
rename src/CSF.Core/{Abstractions => Preconditions}/PreconditionAttribute.cs (89%)
rename src/CSF.Core/{Abstractions/Components/IParameterComponent.cs => Reflection/IArgument.cs} (91%)
rename src/CSF.Core/{Abstractions/Components/IParameterContainer.cs => Reflection/IArgumentBucket.cs} (86%)
rename src/CSF.Core/{Abstractions/Components/IConditionalComponent.cs => Reflection/IConditional.cs} (85%)
rename src/CSF.Core/{Abstractions/Components/IComponent.cs => Reflection/INameable.cs} (87%)
rename src/CSF.Core/{Components/Parameter.cs => Reflection/Impl/ArgumentInfo.cs} (88%)
rename src/CSF.Core/{Components/Command.cs => Reflection/Impl/CommandInfo.cs} (85%)
rename src/CSF.Core/{Components/ComplexParameter.cs => Reflection/Impl/ComplexArgumentInfo.cs} (81%)
rename src/CSF.Core/{Components/Module.cs => Reflection/Impl/ModuleInfo.cs} (81%)
rename src/CSF.Core/TypeReaders/{ => Impl}/BaseTypeReader.cs (97%)
rename src/CSF.Core/TypeReaders/{ => Impl}/ColorTypeReader.cs (94%)
rename src/CSF.Core/TypeReaders/{ => Impl}/EnumTypeReader.cs (89%)
rename src/CSF.Core/TypeReaders/{ => Impl}/TimeSpanTypeReader.cs (94%)
rename src/CSF.Core/{Abstractions => TypeReaders}/TypeReader.cs (61%)
delete mode 100644 src/CSF.Tests.Console/TypeReaders/TimeOnlyTypeReader.cs
diff --git a/src/CSF.Core/Abstractions/Contexts/ICommandContext.cs b/src/CSF.Core/Abstractions/Contexts/ICommandContext.cs
deleted file mode 100644
index 1688d0a..0000000
--- a/src/CSF.Core/Abstractions/Contexts/ICommandContext.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents an interface that supports all implementations of command context classes.
- ///
- public interface ICommandContext
- {
- ///
- /// The command options.
- ///
- public IExecutionOptions Options { get; }
- }
-}
diff --git a/src/CSF.Core/Attributes/DontRegisterAttribute.cs b/src/CSF.Core/Attributes/DontRegisterAttribute.cs
deleted file mode 100644
index a2f8f42..0000000
--- a/src/CSF.Core/Attributes/DontRegisterAttribute.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents an attribute that forces the registration to not register provided member.
- ///
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
- public sealed class DontRegisterAttribute : Attribute
- {
-
- }
-}
diff --git a/src/CSF.Core/Attributes/RequireContextAttribute.cs b/src/CSF.Core/Attributes/RequireContextAttribute.cs
deleted file mode 100644
index 380ae2f..0000000
--- a/src/CSF.Core/Attributes/RequireContextAttribute.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents a precondition that checks if the provided context is valid.
- ///
- ///
- /// Compiles a new from provided .
- ///
- public sealed class RequireContextAttribute() : PreconditionAttribute
- {
- ///
- /// The context type to compare against.
- ///
- public Type ContextType { get; } = typeof(T);
-
- public override ValueTask EvaluateAsync(ICommandContext context, Command command)
- {
- var providedType = context.GetType();
-
- if (providedType != ContextType)
- return ValueTask.FromResult(new CheckResult(new CheckException($"Invalid context was passed into the command. Expected: '{ContextType.FullName}', got '{providedType.FullName}'")));
-
- return ValueTask.FromResult(new CheckResult());
- }
- }
-}
diff --git a/src/CSF.Core/CMBuilder.cs b/src/CSF.Core/CMBuilder.cs
deleted file mode 100644
index c9a9547..0000000
--- a/src/CSF.Core/CMBuilder.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using System.Reflection;
-
-namespace CSF
-{
- public sealed class CMBuilder
- {
- private bool _disposed = false;
-
- public HashSet Assemblies { get; set; } = [Assembly.GetEntryAssembly()];
-
- public HashSet TypeReaders { get; set; } = [];
-
- public CMBuilder AddEntryAssembly()
- {
- if (_disposed)
- ThrowHelpers.InvalidOp("This builder cannot be reused.");
-
- Assemblies.Add(Assembly.GetEntryAssembly());
- return this;
- }
-
- public CMBuilder AddAssembly(Assembly assembly)
- {
- if (_disposed)
- ThrowHelpers.InvalidOp("This builder cannot be reused.");
-
- Assemblies.Add(assembly);
- return this;
- }
-
- public CMBuilder AddTypeReader(TypeReader typeReader)
- {
- if (_disposed)
- ThrowHelpers.InvalidOp("This builder cannot be reused.");
-
- TypeReaders.Add(typeReader);
- return this;
- }
-
- public CommandManager Build()
- {
- var typeReaders = TypeReader.CreateDefaultReaders().UnionBy(TypeReaders, x => x.Type).ToDictionary(x => x.Type, x => x);
-
- if (Assemblies.Count == 0)
- ThrowHelpers.InvalidOp("An assembly has to be present in the builder prior to building the CommandManager.");
-
- IEnumerable BuildComponents()
- {
- var rootReader = typeof(TypeReader);
- foreach (var assembly in Assemblies)
- {
- foreach (var type in assembly.GetTypes())
- {
- if (rootReader.IsAssignableFrom(type)
- && !type.IsAbstract
- && !type.ContainsGenericParameters)
- {
- var reader = Activator.CreateInstance(type) as TypeReader;
-
- // replace existing typereader with replacement handler
- if (!typeReaders.TryAdd(reader.Type, reader))
- typeReaders[reader.Type] = reader;
- }
- }
- }
-
- var rootType = typeof(ModuleBase);
- foreach (var assembly in Assemblies)
- {
- foreach (var type in assembly.GetTypes())
- {
- if (rootType.IsAssignableFrom(type)
- && !type.IsAbstract
- && !type.ContainsGenericParameters)
- {
- yield return new Module(type, typeReaders);
- }
- }
- }
- }
-
- _disposed = true;
-
- return new(BuildComponents().SelectMany(x => x.Components), [.. Assemblies]);
- }
- }
-}
diff --git a/src/CSF.Core/Components/CommandCell.cs b/src/CSF.Core/Components/CommandCell.cs
deleted file mode 100644
index be5a263..0000000
--- a/src/CSF.Core/Components/CommandCell.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents a command resolved through the match and search pipeline.
- ///
- public readonly struct CommandCell
- {
- ///
- /// Gets the command that this cell represents.
- ///
- public Command Command { get; }
-
- ///
- /// Gets the arguments that are to be used to execute the method of this command.
- ///
- public object[] Arguments { get; }
-
- ///
- /// Gets the exception that occurred while resolving this command.
- ///
- ///
- /// if no exception occurred.
- ///
- public Exception Exception { get; }
-
- ///
- /// Gets if the cell is invalid and cannot be executed.
- ///
- public bool IsInvalid { get; }
-
- ///
- /// Creates a new that is constructed when resolvement of a command failed.
- ///
- ///
- public CommandCell(Exception exception)
- : this(null, null)
- {
- Command = null;
- Arguments = null;
-
- Exception = exception;
- IsInvalid = true;
- }
-
- ///
- /// Creates a new that is constructed when resolvement of a command succeeded.
- ///
- ///
- ///
- public CommandCell(Command match, object[] arguments)
- {
- Command = match;
- Arguments = arguments;
-
- Exception = null;
- IsInvalid = false;
- }
- }
-}
diff --git a/src/CSF.Core/Components/Constructor.cs b/src/CSF.Core/Components/Constructor.cs
deleted file mode 100644
index ca971e1..0000000
--- a/src/CSF.Core/Components/Constructor.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Reflection;
-
-namespace CSF
-{
- ///
- /// Represents information about the primary constructor of a component.
- ///
- public class Constructor : IComponent
- {
- ///
- public string Name { get; }
-
- ///
- public Attribute[] Attributes { get; }
-
- ///
- /// Gets the target constructor information that this constructor should call.
- ///
- public ConstructorInfo Target { get; }
-
- internal Constructor(Type type)
- {
- var target = type.GetConstructors()[0];
-
- Attributes = target.GetAttributes(true);
- Name = target.Name;
- Target = target;
- }
-
- ///
- /// Formats the type into a readable signature.
- ///
- /// A string containing a readable signature.
- public override string ToString()
- => $"{Name}";
- }
-}
\ No newline at end of file
diff --git a/src/CSF.Core/Contexts/CommandContext.cs b/src/CSF.Core/Contexts/CommandContext.cs
deleted file mode 100644
index adc03fe..0000000
--- a/src/CSF.Core/Contexts/CommandContext.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace CSF
-{
- public class CommandContext(T options) : CommandContext(options), ICommandContext
- where T : IExecutionOptions
- {
- public new T Options { get; } = options;
-
- IExecutionOptions ICommandContext.Options { get; } = options;
- }
-
- public class CommandContext(IExecutionOptions options) : ICommandContext
- {
- public IExecutionOptions Options { get; } = options;
- }
-}
diff --git a/src/CSF.Core/Attributes/CommandAttribute.cs b/src/CSF.Core/Core/Attributes/CommandAttribute.cs
similarity index 79%
rename from src/CSF.Core/Attributes/CommandAttribute.cs
rename to src/CSF.Core/Core/Attributes/CommandAttribute.cs
index fc01fc2..3be9948 100644
--- a/src/CSF.Core/Attributes/CommandAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/CommandAttribute.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
+using CSF.Helpers;
+using System.Diagnostics.CodeAnalysis;
namespace CSF
{
@@ -18,20 +19,12 @@ public sealed class CommandAttribute : Attribute
///
public string[] Aliases { get; }
- ///
- /// Sets up a new command attribute with the provided name.
- ///
- ///
public CommandAttribute([DisallowNull] string name)
: this(name, [])
{
}
- ///
- /// Sets up a new command attribute with the provided name and aliases.
- ///
- ///
[CLSCompliant(false)]
public CommandAttribute([DisallowNull] string name, params string[] aliases)
{
diff --git a/src/CSF.Core/Attributes/ComplexAttribute.cs b/src/CSF.Core/Core/Attributes/ComplexAttribute.cs
similarity index 100%
rename from src/CSF.Core/Attributes/ComplexAttribute.cs
rename to src/CSF.Core/Core/Attributes/ComplexAttribute.cs
diff --git a/src/CSF.Core/Attributes/DescriptionAttribute.cs b/src/CSF.Core/Core/Attributes/DescriptionAttribute.cs
similarity index 93%
rename from src/CSF.Core/Attributes/DescriptionAttribute.cs
rename to src/CSF.Core/Core/Attributes/DescriptionAttribute.cs
index a183501..89c3064 100644
--- a/src/CSF.Core/Attributes/DescriptionAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/DescriptionAttribute.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
+using CSF.Helpers;
+using System.Diagnostics.CodeAnalysis;
namespace CSF
{
diff --git a/src/CSF.Core/Attributes/GroupAttribute.cs b/src/CSF.Core/Core/Attributes/GroupAttribute.cs
similarity index 96%
rename from src/CSF.Core/Attributes/GroupAttribute.cs
rename to src/CSF.Core/Core/Attributes/GroupAttribute.cs
index f9a15ca..39506e2 100644
--- a/src/CSF.Core/Attributes/GroupAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/GroupAttribute.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
+using CSF.Helpers;
+using System.Diagnostics.CodeAnalysis;
namespace CSF
{
diff --git a/src/CSF.Core/Attributes/PrimaryConstructorAttribute.cs b/src/CSF.Core/Core/Attributes/PrimaryConstructorAttribute.cs
similarity index 100%
rename from src/CSF.Core/Attributes/PrimaryConstructorAttribute.cs
rename to src/CSF.Core/Core/Attributes/PrimaryConstructorAttribute.cs
diff --git a/src/CSF.Core/Attributes/PriorityAttribute.cs b/src/CSF.Core/Core/Attributes/PriorityAttribute.cs
similarity index 100%
rename from src/CSF.Core/Attributes/PriorityAttribute.cs
rename to src/CSF.Core/Core/Attributes/PriorityAttribute.cs
diff --git a/src/CSF.Core/Attributes/RemainderAttribute.cs b/src/CSF.Core/Core/Attributes/RemainderAttribute.cs
similarity index 100%
rename from src/CSF.Core/Attributes/RemainderAttribute.cs
rename to src/CSF.Core/Core/Attributes/RemainderAttribute.cs
diff --git a/src/CSF.Core/Core/CommandConfiguration.cs b/src/CSF.Core/Core/CommandConfiguration.cs
new file mode 100644
index 0000000..20af470
--- /dev/null
+++ b/src/CSF.Core/Core/CommandConfiguration.cs
@@ -0,0 +1,12 @@
+using CSF.TypeReaders;
+using System.Reflection;
+
+namespace CSF
+{
+ public sealed class CommandConfiguration
+ {
+ public Assembly[] Assemblies { get; set; } = [ Assembly.GetEntryAssembly() ];
+
+ public TypeReader[] TypeReaders { get; set; } = [];
+ }
+}
diff --git a/src/CSF.Core/CommandManager.cs b/src/CSF.Core/Core/CommandManager.cs
similarity index 61%
rename from src/CSF.Core/CommandManager.cs
rename to src/CSF.Core/Core/CommandManager.cs
index 256d13b..14ab88f 100644
--- a/src/CSF.Core/CommandManager.cs
+++ b/src/CSF.Core/Core/CommandManager.cs
@@ -1,39 +1,41 @@
-using System.Reflection;
+using Microsoft.Extensions.DependencyInjection;
+using CSF.TypeReaders;
+using CSF.Helpers;
+using CSF.Reflection;
+using CSF.Exceptions;
[assembly: CLSCompliant(true)]
namespace CSF
{
- ///
- /// The root type of the Command Standardization Framework (CSF). This type is responsible for setting up the execution pipeline, handling command input and managing modules.
- ///
- ///
- /// Guides and documentation can be found at:
- ///
public sealed class CommandManager
{
- ///
- /// Gets the components registered to this manager.
- ///
- public HashSet Components { get; }
+ public IServiceProvider Services { get; }
- ///
- /// Gets the assemblies used to register to this manager.
- ///
- public Assembly[] Assemblies { get; }
+ public IReadOnlySet Components { get; }
- public CommandManager(IEnumerable components, Assembly[] assemblies)
+ public TypeReader[] TypeReaders { get; }
+
+ public CommandConfiguration Configuration { get; }
+
+ public CommandManager(IServiceProvider services, CommandConfiguration configuration)
{
- Components = components.ToHashSet();
- Assemblies = assemblies;
+ TypeReaders = configuration.TypeReaders.Distinct().ToArray();
+
+ if (configuration.Assemblies == null || configuration.Assemblies.Length == 0)
+ {
+ ThrowHelpers.ArgMissing(nameof(configuration.Assemblies));
+ }
+
+ Components = BuildComponents(configuration)
+ .SelectMany(x => x.Components)
+ .ToHashSet();
+
+ Services = services;
+
+ Configuration = configuration;
}
- ///
- ///
- ///
- ///
- ///
- ///
public async ValueTask ExecuteAsync(ICommandContext context, params object[] args)
{
// search all relevant commands.
@@ -60,11 +62,6 @@ public async ValueTask ExecuteAsync(ICommandContext context, params obj
return fallback;
}
- ///
- ///
- ///
- ///
- ///
public IEnumerable Search(object[] args)
{
// recursively search for commands in the execution.
@@ -72,7 +69,7 @@ public IEnumerable Search(object[] args)
}
#region Matching
- internal static async ValueTask MatchAsync(ICommandContext context, SearchResult search, object[] args)
+ private static async ValueTask MatchAsync(ICommandContext context, SearchResult search, object[] args)
{
// check command preconditions.
var check = await CheckAsync(context, search.Command);
@@ -101,7 +98,7 @@ internal static async ValueTask MatchAsync(ICommandContext context,
#endregion
#region Reading
- internal static async ValueTask ReadAsync(ICommandContext context, SearchResult search, object[] args)
+ private static async ValueTask ReadAsync(ICommandContext context, SearchResult search, object[] args)
{
// skip if no parameters exist.
if (!search.Command.HasParameters)
@@ -128,7 +125,7 @@ internal static async ValueTask ReadAsync(ICommandContext context,
#endregion
#region Checking
- internal static async ValueTask CheckAsync(ICommandContext context, Command command)
+ private static async ValueTask CheckAsync(ICommandContext context, CommandInfo command)
{
foreach (var precon in command.Preconditions)
{
@@ -142,14 +139,15 @@ internal static async ValueTask CheckAsync(ICommandContext context,
#endregion
#region Running
- internal static async ValueTask RunAsync(ICommandContext context, MatchResult match)
+ private async ValueTask RunAsync(ICommandContext context, MatchResult match)
{
try
{
- var module = context.Options.Scope.ServiceProvider.GetService(match.Command.Module.Type) as ModuleBase;
+ var module = Services.GetService(match.Command.Module.Type) as ModuleBase;
module.Context = context;
module.Command = match.Command;
+ module.Services = Services;
await module.BeforeExecuteAsync();
@@ -165,5 +163,40 @@ internal static async ValueTask RunAsync(ICommandContext context, Mat
}
}
#endregion
+
+ #region Building
+ private IEnumerable BuildComponents(CommandConfiguration configuration)
+ {
+ var typeReaders = TypeReader.CreateDefaultReaders().UnionBy(TypeReaders, x => x.Type).ToDictionary(x => x.Type, x => x);
+
+ var rootType = typeof(ModuleBase);
+ foreach (var assembly in configuration.Assemblies)
+ {
+ foreach (var type in assembly.GetTypes())
+ {
+ if (rootType.IsAssignableFrom(type)
+ && !type.IsAbstract
+ && !type.ContainsGenericParameters)
+ {
+ yield return new ModuleInfo(type, typeReaders);
+ }
+ }
+ }
+ }
+ #endregion
+ }
+
+ public static class CollectionExtensions
+ {
+ public static IServiceCollection WithCommandManager(this IServiceCollection collection, Action action = null)
+ {
+ var context = new CommandConfiguration();
+
+ action?.Invoke(context);
+
+ collection.AddCommandManager(context);
+
+ return collection;
+ }
}
}
diff --git a/src/CSF.Core/Core/Execution/ICommandContext.cs b/src/CSF.Core/Core/Execution/ICommandContext.cs
new file mode 100644
index 0000000..b02d152
--- /dev/null
+++ b/src/CSF.Core/Core/Execution/ICommandContext.cs
@@ -0,0 +1,8 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace CSF
+{
+ public interface ICommandContext
+ {
+ }
+}
diff --git a/src/CSF.Core/Core/Execution/Impl/CommandContext.cs b/src/CSF.Core/Core/Execution/Impl/CommandContext.cs
new file mode 100644
index 0000000..e0b04e7
--- /dev/null
+++ b/src/CSF.Core/Core/Execution/Impl/CommandContext.cs
@@ -0,0 +1,7 @@
+namespace CSF
+{
+ public class CommandContext : ICommandContext
+ {
+
+ }
+}
diff --git a/src/CSF.Core/Abstractions/ModuleBase.cs b/src/CSF.Core/Core/Execution/ModuleBase.cs
similarity index 91%
rename from src/CSF.Core/Abstractions/ModuleBase.cs
rename to src/CSF.Core/Core/Execution/ModuleBase.cs
index 09df4bb..2640f64 100644
--- a/src/CSF.Core/Abstractions/ModuleBase.cs
+++ b/src/CSF.Core/Core/Execution/ModuleBase.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using CSF.Reflection;
+
+namespace CSF
{
///
/// Represents a registration and execution tool for modules.
@@ -32,16 +34,12 @@ public abstract class ModuleBase
///
/// Gets the command execution services as provided by the command scope.
///
- public IServiceProvider Services
- {
- get
- => Context.Options.Scope.ServiceProvider;
- }
+ public IServiceProvider Services { get; internal set; }
///
/// Gets the component that displays all information about the command thats currently in scope.
///
- public Command Command { get; internal set; }
+ public CommandInfo Command { get; internal set; }
///
/// Responds to the command with a message.
diff --git a/src/CSF.Core/Abstractions/Results/IResult.cs b/src/CSF.Core/Core/Results/IResult.cs
similarity index 100%
rename from src/CSF.Core/Abstractions/Results/IResult.cs
rename to src/CSF.Core/Core/Results/IResult.cs
diff --git a/src/CSF.Core/Results/CheckResult.cs b/src/CSF.Core/Core/Results/Impl/CheckResult.cs
similarity index 100%
rename from src/CSF.Core/Results/CheckResult.cs
rename to src/CSF.Core/Core/Results/Impl/CheckResult.cs
diff --git a/src/CSF.Core/Results/MatchResult.cs b/src/CSF.Core/Core/Results/Impl/MatchResult.cs
similarity index 66%
rename from src/CSF.Core/Results/MatchResult.cs
rename to src/CSF.Core/Core/Results/Impl/MatchResult.cs
index 83bf250..89b26ff 100644
--- a/src/CSF.Core/Results/MatchResult.cs
+++ b/src/CSF.Core/Core/Results/Impl/MatchResult.cs
@@ -1,23 +1,25 @@
-namespace CSF
+using CSF.Reflection;
+
+namespace CSF
{
public readonly struct MatchResult : IResult
{
public Exception Exception { get; } = null;
- public Command Command { get; }
+ public CommandInfo Command { get; }
public object[] Reads { get; }
public bool Success { get; }
- internal MatchResult(Command command, object[] reads)
+ internal MatchResult(CommandInfo command, object[] reads)
{
Command = command;
Reads = reads;
Success = true;
}
- internal MatchResult(Command command, Exception exception)
+ internal MatchResult(CommandInfo command, Exception exception)
{
Command = command;
Reads = null;
diff --git a/src/CSF.Core/Results/ReadResult.cs b/src/CSF.Core/Core/Results/Impl/ReadResult.cs
similarity index 100%
rename from src/CSF.Core/Results/ReadResult.cs
rename to src/CSF.Core/Core/Results/Impl/ReadResult.cs
diff --git a/src/CSF.Core/Results/RunResult.cs b/src/CSF.Core/Core/Results/Impl/RunResult.cs
similarity index 66%
rename from src/CSF.Core/Results/RunResult.cs
rename to src/CSF.Core/Core/Results/Impl/RunResult.cs
index 688e9a4..cac745c 100644
--- a/src/CSF.Core/Results/RunResult.cs
+++ b/src/CSF.Core/Core/Results/Impl/RunResult.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using CSF.Reflection;
+
+namespace CSF
{
public readonly struct RunResult : IResult
{
@@ -6,18 +8,18 @@
public object ReturnType { get; } = null;
- public Command Command { get; }
+ public CommandInfo Command { get; }
public bool Success { get; }
- internal RunResult(Command command, Exception exception)
+ internal RunResult(CommandInfo command, Exception exception)
{
Exception = exception;
Command = command;
Success = false;
}
- internal RunResult(Command command, object returnValue)
+ internal RunResult(CommandInfo command, object returnValue)
{
ReturnType = returnValue;
Command = command;
diff --git a/src/CSF.Core/Results/SearchResult.cs b/src/CSF.Core/Core/Results/Impl/SearchResult.cs
similarity index 63%
rename from src/CSF.Core/Results/SearchResult.cs
rename to src/CSF.Core/Core/Results/Impl/SearchResult.cs
index 6bf7f47..a0605e0 100644
--- a/src/CSF.Core/Results/SearchResult.cs
+++ b/src/CSF.Core/Core/Results/Impl/SearchResult.cs
@@ -1,17 +1,22 @@
-namespace CSF
+using CSF.Reflection;
+
+namespace CSF
{
public readonly struct SearchResult : IResult
{
public Exception Exception { get; } = null;
- public Command Command { get; }
+ public CommandInfo Command { get; }
+
+ public bool Success { get; }
internal int SearchHeight { get; }
- internal SearchResult(Command command, int srcHeight)
+ internal SearchResult(CommandInfo command, int srcHeight)
{
Command = command;
SearchHeight = srcHeight;
+ Success = true;
}
internal SearchResult(Exception exception)
@@ -19,6 +24,7 @@ internal SearchResult(Exception exception)
Exception = exception;
Command = null;
SearchHeight = 0;
+ Success = false;
}
}
}
diff --git a/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs b/src/CSF.Core/Exceptions/ArgumentMissingException.cs
similarity index 84%
rename from src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs
rename to src/CSF.Core/Exceptions/ArgumentMissingException.cs
index e57be6e..a78d6ed 100644
--- a/src/CSF.Core/Exceptions/Internal/ArgumentMissingException.cs
+++ b/src/CSF.Core/Exceptions/ArgumentMissingException.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Exceptions
{
internal sealed class ArgumentMissingException(string paramName, string message)
: ArgumentException(message, paramName)
diff --git a/src/CSF.Core/Exceptions/CheckException.cs b/src/CSF.Core/Exceptions/CheckException.cs
index 8b97621..7ed48d0 100644
--- a/src/CSF.Core/Exceptions/CheckException.cs
+++ b/src/CSF.Core/Exceptions/CheckException.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Exceptions
{
///
/// Represents a that is thrown when no matched command succeeded its precondition checks.
diff --git a/src/CSF.Core/Exceptions/CommandException.cs b/src/CSF.Core/Exceptions/CommandException.cs
index 8ae1c6e..52b9658 100644
--- a/src/CSF.Core/Exceptions/CommandException.cs
+++ b/src/CSF.Core/Exceptions/CommandException.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Exceptions
{
///
/// Represents a that is thrown when the command being executed failed to run its body.
diff --git a/src/CSF.Core/Abstractions/ExecutionException.cs b/src/CSF.Core/Exceptions/ExecutionException.cs
similarity index 77%
rename from src/CSF.Core/Abstractions/ExecutionException.cs
rename to src/CSF.Core/Exceptions/ExecutionException.cs
index 05bffdc..609dc83 100644
--- a/src/CSF.Core/Abstractions/ExecutionException.cs
+++ b/src/CSF.Core/Exceptions/ExecutionException.cs
@@ -1,6 +1,6 @@
-namespace CSF
+namespace CSF.Exceptions
{
- public abstract class ExecutionException : Exception
+ public class ExecutionException : Exception
{
public ExecutionException(string message)
: base(message)
diff --git a/src/CSF.Core/Exceptions/MatchException.cs b/src/CSF.Core/Exceptions/MatchException.cs
index 8fd43e6..6dd0fae 100644
--- a/src/CSF.Core/Exceptions/MatchException.cs
+++ b/src/CSF.Core/Exceptions/MatchException.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Exceptions
{
public class MatchException(string message, Exception innerException = null)
: ExecutionException(message, innerException)
diff --git a/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs b/src/CSF.Core/Exceptions/RangeDuplicateException.cs
similarity index 84%
rename from src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs
rename to src/CSF.Core/Exceptions/RangeDuplicateException.cs
index 1a799ab..9d3d4ba 100644
--- a/src/CSF.Core/Exceptions/Internal/RangeDuplicateException.cs
+++ b/src/CSF.Core/Exceptions/RangeDuplicateException.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Exceptions
{
internal sealed class RangeDuplicateException(string paramName, string message)
: ArgumentException(paramName, message)
diff --git a/src/CSF.Core/Exceptions/ReadException.cs b/src/CSF.Core/Exceptions/ReadException.cs
index b0a3320..1901f6f 100644
--- a/src/CSF.Core/Exceptions/ReadException.cs
+++ b/src/CSF.Core/Exceptions/ReadException.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Exceptions
{
///
/// Represents a that is thrown when no matched command succeeded parsing its parameters.
diff --git a/src/CSF.Core/Exceptions/SearchException.cs b/src/CSF.Core/Exceptions/SearchException.cs
index ac55443..64cbff9 100644
--- a/src/CSF.Core/Exceptions/SearchException.cs
+++ b/src/CSF.Core/Exceptions/SearchException.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Exceptions
{
///
/// Represents a that is thrown when no command could be found.
diff --git a/src/CSF.Core/ExecutionOptions.cs b/src/CSF.Core/ExecutionOptions.cs
deleted file mode 100644
index e4fd49c..0000000
--- a/src/CSF.Core/ExecutionOptions.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace CSF
-{
- public interface IExecutionOptions
- {
- ///
- /// Gets or sets the service scope to be used in command execution. The services used for executing commands will be defaulted to the globally defined if this property is not set.
- ///
- public IServiceScope Scope { get; set; }
- }
-
- ///
- /// Represents a set of options that change the execution flow of the command handler.
- ///
- public class ExecutionOptions : IExecutionOptions
- {
- ///
- /// Gets or sets the service scope to be used in command execution. The services used for executing commands will be defaulted to the globally defined if this property is not set.
- ///
- public IServiceScope Scope { get; set; } = null;
-
- public ExecutionOptions(IServiceProvider provider)
- {
- Scope = provider.CreateScope();
- }
- }
-}
diff --git a/src/CSF.Core/Helpers/Internal/LinqHelpers.cs b/src/CSF.Core/Helpers/CollectionHelpers.cs
similarity index 94%
rename from src/CSF.Core/Helpers/Internal/LinqHelpers.cs
rename to src/CSF.Core/Helpers/CollectionHelpers.cs
index b692268..5527935 100644
--- a/src/CSF.Core/Helpers/Internal/LinqHelpers.cs
+++ b/src/CSF.Core/Helpers/CollectionHelpers.cs
@@ -1,8 +1,8 @@
using System.Collections;
-namespace CSF
+namespace CSF.Helpers
{
- internal static class LinqHelpers
+ internal static class CollectionHelpers
{
public static IEnumerable CastWhere(this IEnumerable input)
{
diff --git a/src/CSF.Core/CommandManagerHelper.cs b/src/CSF.Core/Helpers/ExecutionHelpers.cs
similarity index 80%
rename from src/CSF.Core/CommandManagerHelper.cs
rename to src/CSF.Core/Helpers/ExecutionHelpers.cs
index 6bd1f61..8048168 100644
--- a/src/CSF.Core/CommandManagerHelper.cs
+++ b/src/CSF.Core/Helpers/ExecutionHelpers.cs
@@ -1,8 +1,10 @@
-namespace CSF
+using CSF.Reflection;
+
+namespace CSF.Helpers
{
- internal static class CommandManagerHelper
+ internal static class ExecutionHelpers
{
- public static IEnumerable RecursiveSearch(this IEnumerable components, object[] args, int searchHeight)
+ public static IEnumerable RecursiveSearch(this IEnumerable components, object[] args, int searchHeight)
{
List discovered = [];
@@ -11,7 +13,7 @@ public static IEnumerable RecursiveSearch(this IEnumerable RecursiveSearch(this IEnumerable RecursiveSearch(this IEnumerable RecursiveReadAsync(this IParameterComponent[] param, ICommandContext context, object[] args, int index)
+ public static async Task RecursiveReadAsync(this IArgument[] param, ICommandContext context, object[] args, int index)
{
-
- static async ValueTask ReadAsync(IParameterComponent param, ICommandContext context, object arg)
+ static async ValueTask ReadAsync(IArgument param, ICommandContext context, object arg)
{
if (arg.GetType() == param.Type)
return new(arg);
@@ -64,7 +65,7 @@ static async ValueTask ReadAsync(IParameterComponent param, ICommand
continue;
}
- if (parameter is ComplexParameter complex)
+ if (parameter is ComplexArgumentInfo complex)
{
var result = await complex.Parameters.RecursiveReadAsync(context, args, index);
@@ -74,7 +75,7 @@ static async ValueTask ReadAsync(IParameterComponent param, ICommand
{
try
{
- var obj = complex.Constructor.Target.Invoke(result.Select(x => x.Value).ToArray());
+ var obj = complex.Constructor.Invoke(result.Select(x => x.Value).ToArray());
results[i] = new(obj);
}
catch (Exception ex)
diff --git a/src/CSF.Core/Helpers/Internal/ReflectionHelpers.cs b/src/CSF.Core/Helpers/ReflectionHelpers.cs
similarity index 60%
rename from src/CSF.Core/Helpers/Internal/ReflectionHelpers.cs
rename to src/CSF.Core/Helpers/ReflectionHelpers.cs
index eac4ced..b753049 100644
--- a/src/CSF.Core/Helpers/Internal/ReflectionHelpers.cs
+++ b/src/CSF.Core/Helpers/ReflectionHelpers.cs
@@ -1,10 +1,13 @@
-using System.Reflection;
+using CSF.TypeReaders;
+using CSF.Reflection;
+using System.Reflection;
+using CSF.Preconditions;
-namespace CSF
+namespace CSF.Helpers
{
internal static class ReflectionHelpers
{
- private static IEnumerable GetModules(Module module, IDictionary typeReaders)
+ private static IEnumerable GetModules(ModuleInfo module, IDictionary typeReaders)
{
foreach (var group in module.Type.GetNestedTypes())
{
@@ -12,13 +15,13 @@ private static IEnumerable GetModules(Module module, IDictionary GetCommands(Module module, IDictionary typeReaders)
+ private static IEnumerable GetCommands(ModuleInfo module, IDictionary typeReaders)
{
foreach (var method in module.Type.GetMethods())
{
@@ -31,12 +34,6 @@ private static IEnumerable GetCommands(Module module, IDictionary GetCommands(Module module, IDictionary typeReaders)
+ public static IConditional[] GetComponents(this ModuleInfo module, IDictionary typeReaders)
{
- var commands = (IEnumerable)GetCommands(module, typeReaders)
+ var commands = (IEnumerable)GetCommands(module, typeReaders)
.OrderBy(x => x.Parameters.Length);
- var modules = (IEnumerable)GetModules(module, typeReaders)
+ var modules = (IEnumerable)GetModules(module, typeReaders)
.OrderBy(x => x.Components.Length);
return commands.Concat(modules)
.ToArray();
}
- public static IParameterComponent[] GetParameters(this MethodBase method, IDictionary typeReaders)
+ public static IArgument[] GetParameters(this MethodBase method, IDictionary typeReaders)
{
var parameters = method.GetParameters();
- var arr = new IParameterComponent[parameters.Length];
+ var arr = new IArgument[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].GetCustomAttributes().Any(x => x is ComplexAttribute))
{
- arr[i] = new ComplexParameter(parameters[i], typeReaders);
+ arr[i] = new ComplexArgumentInfo(parameters[i], typeReaders);
}
else
{
- arr[i] = new Parameter(parameters[i], typeReaders);
+ arr[i] = new ArgumentInfo(parameters[i], typeReaders);
}
}
@@ -86,20 +83,20 @@ public static PreconditionAttribute[] GetPreconditions(this Attribute[] attribut
public static Attribute[] GetAttributes(this ICustomAttributeProvider provider, bool inherit)
=> provider.GetCustomAttributes(inherit).CastWhere().ToArray();
- public static Tuple GetLength(this IParameterComponent[] parameters)
+ public static Tuple GetLength(this IArgument[] parameters)
{
var minLength = 0;
var maxLength = 0;
foreach (var parameter in parameters)
{
- if (parameter is ComplexParameter complexParam)
+ if (parameter is ComplexArgumentInfo complexParam)
{
maxLength += complexParam.MaxLength;
minLength += complexParam.MinLength;
}
- if (parameter is Parameter defaultParam)
+ if (parameter is ArgumentInfo defaultParam)
{
maxLength++;
if (!defaultParam.IsOptional)
diff --git a/src/CSF.Core/Helpers/ServiceHelper.cs b/src/CSF.Core/Helpers/ServiceHelper.cs
deleted file mode 100644
index 465577a..0000000
--- a/src/CSF.Core/Helpers/ServiceHelper.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using System.ComponentModel;
-
-namespace CSF
-{
- public static class ServiceHelper
- {
- ///
- /// Includes inheriting into the this method is called on.
- ///
- /// The type inheriting to include in the collection.
- ///
- /// The configuration required to set up a new instance of .
- /// The same for chained calls.
- public static IServiceCollection WithCommandManager(this IServiceCollection collection, Action action = null)
- {
- var context = new CMBuilder();
-
- action?.Invoke(context);
-
- collection.AddCommandManager(context);
-
- return collection;
- }
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- public static IServiceCollection AddCommandManager(this IServiceCollection collection, CMBuilder configuration)
- {
- ModulesAddTransient(collection, configuration);
-
- var implementor = configuration.Build();
-
- collection.TryAddSingleton(implementor);
-
- return collection;
- }
-
- private static IServiceCollection ModulesAddTransient(IServiceCollection collection, CMBuilder configuration)
- {
- var rootType = typeof(ModuleBase);
-
- foreach (var assembly in configuration.Assemblies)
- foreach (var type in assembly.GetTypes())
- if (rootType.IsAssignableFrom(type) && !type.IsAbstract && !type.ContainsGenericParameters)
- collection.TryAddTransient(type);
-
- return collection;
- }
- }
-}
diff --git a/src/CSF.Core/Helpers/ServiceHelpers.cs b/src/CSF.Core/Helpers/ServiceHelpers.cs
new file mode 100644
index 0000000..b928f64
--- /dev/null
+++ b/src/CSF.Core/Helpers/ServiceHelpers.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace CSF.Helpers
+{
+ internal static class ServiceHelpers
+ {
+ public static IServiceCollection AddCommandManager(this IServiceCollection collection, CommandConfiguration configuration)
+ {
+ collection.ModulesAddTransient(configuration);
+
+ collection.TryAddSingleton(configuration);
+ collection.TryAddSingleton();
+
+ return collection;
+ }
+
+ public static IServiceCollection ModulesAddTransient(this IServiceCollection collection, CommandConfiguration configuration)
+ {
+ var rootType = typeof(ModuleBase);
+
+ foreach (var assembly in configuration.Assemblies)
+ foreach (var type in assembly.GetTypes())
+ if (rootType.IsAssignableFrom(type) && !type.IsAbstract && !type.ContainsGenericParameters)
+ collection.TryAddTransient(type);
+
+ return collection;
+ }
+ }
+}
diff --git a/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs b/src/CSF.Core/Helpers/ThrowHelpers.cs
similarity index 90%
rename from src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
rename to src/CSF.Core/Helpers/ThrowHelpers.cs
index e039d61..2617c61 100644
--- a/src/CSF.Core/Helpers/Internal/ThrowHelpers.cs
+++ b/src/CSF.Core/Helpers/ThrowHelpers.cs
@@ -1,7 +1,8 @@
-using System.Diagnostics.CodeAnalysis;
+using CSF.Exceptions;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
-namespace CSF
+namespace CSF.Helpers
{
internal static class ThrowHelpers
{
diff --git a/src/CSF.Core/Parsing/Impl/CharSpanParser.cs b/src/CSF.Core/Parsing/Impl/CharSpanParser.cs
new file mode 100644
index 0000000..869679c
--- /dev/null
+++ b/src/CSF.Core/Parsing/Impl/CharSpanParser.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Parsing
+{
+ public sealed class CharSpanParser : SpanParser
+ {
+ public override object[] Parse(Span value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/CSF.Core/Parsing/Impl/StringParser.cs b/src/CSF.Core/Parsing/Impl/StringParser.cs
new file mode 100644
index 0000000..df52e51
--- /dev/null
+++ b/src/CSF.Core/Parsing/Impl/StringParser.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Parsing
+{
+ public class StringParser : Parser
+ {
+ private static readonly SpanParser _spanParser = new CharSpanParser();
+
+ public override object[] Parse(string value)
+ {
+
+ }
+ }
+}
diff --git a/src/CSF.Core/Parsing/Parser.cs b/src/CSF.Core/Parsing/Parser.cs
index 9ee15c1..dbbed4f 100644
--- a/src/CSF.Core/Parsing/Parser.cs
+++ b/src/CSF.Core/Parsing/Parser.cs
@@ -1,9 +1,8 @@
-namespace CSF
+namespace CSF.Parsing
{
- public abstract class Parser
+ public abstract class Parser
+ where T : IEquatable
{
- public static Parser Text { get; } = new TextParser();
-
- public abstract ParserCell Parse(string rawInput);
+ public abstract object[] Parse(T value);
}
}
diff --git a/src/CSF.Core/Parsing/ParserCell.cs b/src/CSF.Core/Parsing/ParserCell.cs
deleted file mode 100644
index 618f089..0000000
--- a/src/CSF.Core/Parsing/ParserCell.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents the information received from a successful operation.
- ///
- public readonly struct ParserCell
- {
- ///
- /// The name of the command.
- ///
- public string Name { get; }
-
- ///
- /// The parameters found through parsing.
- ///
- public string[] Parameters { get; }
-
- ///
- /// The named parameters found through parsing.
- ///
- public IReadOnlyDictionary NamedParameters { get; }
-
- public ParserCell(string[] param, IReadOnlyDictionary namedParam = null)
- {
- Parameters = param[1..];
- NamedParameters = namedParam;
-
- Name = param[0];
- }
- }
-}
diff --git a/src/CSF.Core/Parsing/SpanParser.cs b/src/CSF.Core/Parsing/SpanParser.cs
new file mode 100644
index 0000000..239209e
--- /dev/null
+++ b/src/CSF.Core/Parsing/SpanParser.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Parsing
+{
+ public abstract class SpanParser : Parser
+ where T : struct, IEquatable
+ {
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override object[] Parse(T value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public abstract object[] Parse(Span value);
+ }
+}
diff --git a/src/CSF.Core/Abstractions/PreconditionAttribute.cs b/src/CSF.Core/Preconditions/PreconditionAttribute.cs
similarity index 89%
rename from src/CSF.Core/Abstractions/PreconditionAttribute.cs
rename to src/CSF.Core/Preconditions/PreconditionAttribute.cs
index 61e71bd..b2c48e3 100644
--- a/src/CSF.Core/Abstractions/PreconditionAttribute.cs
+++ b/src/CSF.Core/Preconditions/PreconditionAttribute.cs
@@ -1,6 +1,9 @@
-using System.Diagnostics.CodeAnalysis;
+using CSF.Reflection;
+using CSF.Exceptions;
+using CSF.Helpers;
+using System.Diagnostics.CodeAnalysis;
-namespace CSF
+namespace CSF.Preconditions
{
///
/// Defines a precondition attribute.
@@ -14,9 +17,8 @@ public abstract class PreconditionAttribute : Attribute
///
/// The command context used to execute the command currently in scope.
/// The command that is to be executed if this and all other precondition evaluations succeed.
- /// The services in scope for the current command execution.
/// A result that represents the outcome of the evaluation.
- public abstract ValueTask EvaluateAsync(ICommandContext context, Command command);
+ public abstract ValueTask EvaluateAsync(ICommandContext context, CommandInfo command);
public static CheckResult Error([DisallowNull] Exception exception)
{
diff --git a/src/CSF.Core/Abstractions/Components/IParameterComponent.cs b/src/CSF.Core/Reflection/IArgument.cs
similarity index 91%
rename from src/CSF.Core/Abstractions/Components/IParameterComponent.cs
rename to src/CSF.Core/Reflection/IArgument.cs
index 34453f0..da78714 100644
--- a/src/CSF.Core/Abstractions/Components/IParameterComponent.cs
+++ b/src/CSF.Core/Reflection/IArgument.cs
@@ -1,9 +1,11 @@
-namespace CSF
+using CSF.TypeReaders;
+
+namespace CSF.Reflection
{
///
/// Represents a constructor or method parameter.
///
- public interface IParameterComponent : IComponent
+ public interface IArgument : INameable
{
///
/// Gets the type of the parameter.
diff --git a/src/CSF.Core/Abstractions/Components/IParameterContainer.cs b/src/CSF.Core/Reflection/IArgumentBucket.cs
similarity index 86%
rename from src/CSF.Core/Abstractions/Components/IParameterContainer.cs
rename to src/CSF.Core/Reflection/IArgumentBucket.cs
index 2c0e04e..87d9c46 100644
--- a/src/CSF.Core/Abstractions/Components/IParameterContainer.cs
+++ b/src/CSF.Core/Reflection/IArgumentBucket.cs
@@ -1,14 +1,14 @@
-namespace CSF
+namespace CSF.Reflection
{
///
/// Represents a container that holds and handles parameters.
///
- public interface IParameterContainer
+ public interface IArgumentBucket
{
///
/// Gets a list of parameters for this container.
///
- public IParameterComponent[] Parameters { get; }
+ public IArgument[] Parameters { get; }
///
/// Gets if this container contains any parameters or not.
diff --git a/src/CSF.Core/Abstractions/Components/IConditionalComponent.cs b/src/CSF.Core/Reflection/IConditional.cs
similarity index 85%
rename from src/CSF.Core/Abstractions/Components/IConditionalComponent.cs
rename to src/CSF.Core/Reflection/IConditional.cs
index 9a226c3..4171dcd 100644
--- a/src/CSF.Core/Abstractions/Components/IConditionalComponent.cs
+++ b/src/CSF.Core/Reflection/IConditional.cs
@@ -1,9 +1,11 @@
-namespace CSF
+using CSF.Preconditions;
+
+namespace CSF.Reflection
{
///
/// Represents a component with preconditions available.
///
- public interface IConditionalComponent : IComponent
+ public interface IConditional : INameable
{
///
/// Gets the aliases of this component.
diff --git a/src/CSF.Core/Abstractions/Components/IComponent.cs b/src/CSF.Core/Reflection/INameable.cs
similarity index 87%
rename from src/CSF.Core/Abstractions/Components/IComponent.cs
rename to src/CSF.Core/Reflection/INameable.cs
index 8098c7a..dfee656 100644
--- a/src/CSF.Core/Abstractions/Components/IComponent.cs
+++ b/src/CSF.Core/Reflection/INameable.cs
@@ -1,9 +1,9 @@
-namespace CSF
+namespace CSF.Reflection
{
///
/// Represents any part of a command.
///
- public interface IComponent
+ public interface INameable
{
///
/// Gets the name of the component in question.
diff --git a/src/CSF.Core/Components/Parameter.cs b/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
similarity index 88%
rename from src/CSF.Core/Components/Parameter.cs
rename to src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
index 0312fee..10ab12a 100644
--- a/src/CSF.Core/Components/Parameter.cs
+++ b/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
@@ -1,11 +1,13 @@
-using System.Reflection;
+using CSF.Helpers;
+using CSF.TypeReaders;
+using System.Reflection;
-namespace CSF
+namespace CSF.Reflection
{
///
/// Represents a single parameter for the method.
///
- public sealed class Parameter : IParameterComponent
+ public sealed class ArgumentInfo : IArgument
{
///
public string Name { get; }
@@ -31,7 +33,7 @@ public sealed class Parameter : IParameterComponent
///
public TypeReader TypeReader { get; }
- internal Parameter(ParameterInfo parameterInfo, IDictionary typeReaders)
+ internal ArgumentInfo(ParameterInfo parameterInfo, IDictionary typeReaders)
{
var underlying = Nullable.GetUnderlyingType(parameterInfo.ParameterType);
var attributes = parameterInfo.GetAttributes(false);
diff --git a/src/CSF.Core/Components/Command.cs b/src/CSF.Core/Reflection/Impl/CommandInfo.cs
similarity index 85%
rename from src/CSF.Core/Components/Command.cs
rename to src/CSF.Core/Reflection/Impl/CommandInfo.cs
index b7f2aa5..40cee11 100644
--- a/src/CSF.Core/Components/Command.cs
+++ b/src/CSF.Core/Reflection/Impl/CommandInfo.cs
@@ -1,11 +1,14 @@
-using System.Reflection;
+using CSF.Helpers;
+using CSF.Preconditions;
+using CSF.TypeReaders;
+using System.Reflection;
-namespace CSF
+namespace CSF.Reflection
{
///
/// Represents the information required to execute commands.
///
- public sealed class Command : IConditionalComponent, IParameterContainer
+ public sealed class CommandInfo : IConditional, IArgumentBucket
{
///
public string Name { get; }
@@ -20,7 +23,7 @@ public sealed class Command : IConditionalComponent, IParameterContainer
public bool HasPreconditions { get; }
///
- public IParameterComponent[] Parameters { get; }
+ public IArgument[] Parameters { get; }
///
public bool HasParameters { get; }
@@ -45,14 +48,14 @@ public sealed class Command : IConditionalComponent, IParameterContainer
///
/// Gets the module this command is declared in.
///
- public Module Module { get; }
+ public ModuleInfo Module { get; }
///
/// Gets the target method this command is aimed to execute.
///
public MethodInfo Target { get; }
- internal Command(Module module, MethodInfo method, string[] aliases, IDictionary typeReaders)
+ internal CommandInfo(ModuleInfo module, MethodInfo method, string[] aliases, IDictionary typeReaders)
{
var attributes = method.GetAttributes(true);
var preconditions = attributes.GetPreconditions();
@@ -93,6 +96,6 @@ internal Command(Module module, MethodInfo method, string[] aliases, IDictionary
///
/// A string containing a readable signature.
public override string ToString()
- => $"{Module}.{Target.Name}['{Name}']({string.Join(", ", Parameters)})";
+ => $"{Module}.{Target.Name}['{Name}']({string.Join(", ", Parameters)})";
}
}
diff --git a/src/CSF.Core/Components/ComplexParameter.cs b/src/CSF.Core/Reflection/Impl/ComplexArgumentInfo.cs
similarity index 81%
rename from src/CSF.Core/Components/ComplexParameter.cs
rename to src/CSF.Core/Reflection/Impl/ComplexArgumentInfo.cs
index a1051ab..ed70bff 100644
--- a/src/CSF.Core/Components/ComplexParameter.cs
+++ b/src/CSF.Core/Reflection/Impl/ComplexArgumentInfo.cs
@@ -1,11 +1,13 @@
-using System.Reflection;
+using CSF.Helpers;
+using CSF.TypeReaders;
+using System.Reflection;
-namespace CSF
+namespace CSF.Reflection
{
///
/// Represents a complex parameter, containing a number of its own parameters.
///
- public class ComplexParameter : IParameterComponent, IParameterContainer
+ public class ComplexArgumentInfo : IArgument, IArgumentBucket
{
///
public string Name { get; }
@@ -29,7 +31,7 @@ public class ComplexParameter : IParameterComponent, IParameterContainer
public Attribute[] Attributes { get; }
///
- public IParameterComponent[] Parameters { get; }
+ public IArgument[] Parameters { get; }
///
public bool HasParameters { get; }
@@ -46,9 +48,9 @@ public class ComplexParameter : IParameterComponent, IParameterContainer
///
/// Gets the constructor that constructs this complex parameter.
///
- public Constructor Constructor { get; }
+ public ConstructorInfo Constructor { get; }
- internal ComplexParameter(ParameterInfo parameterInfo, IDictionary typeReaders)
+ internal ComplexArgumentInfo(ParameterInfo parameterInfo, IDictionary typeReaders)
{
var underlying = Nullable.GetUnderlyingType(parameterInfo.ParameterType);
var attributes = parameterInfo.GetAttributes(false);
@@ -69,8 +71,8 @@ internal ComplexParameter(ParameterInfo parameterInfo, IDictionary 0)
{
@@ -100,6 +102,6 @@ internal ComplexParameter(ParameterInfo parameterInfo, IDictionary
/// A string containing a readable signature.
public override string ToString()
- => $"{Type.Name} ({string.Join(", ", Parameters)}) {Name}";
+ => $"{Type.Name} ({string.Join(", ", Parameters)}) {Name}";
}
}
diff --git a/src/CSF.Core/Components/Module.cs b/src/CSF.Core/Reflection/Impl/ModuleInfo.cs
similarity index 81%
rename from src/CSF.Core/Components/Module.cs
rename to src/CSF.Core/Reflection/Impl/ModuleInfo.cs
index e8016cb..b8ccde2 100644
--- a/src/CSF.Core/Components/Module.cs
+++ b/src/CSF.Core/Reflection/Impl/ModuleInfo.cs
@@ -1,9 +1,13 @@
-namespace CSF
+using CSF.Helpers;
+using CSF.Preconditions;
+using CSF.TypeReaders;
+
+namespace CSF.Reflection
{
///
/// Represents information about the module this command is executed in.
///
- public sealed class Module : IConditionalComponent
+ public sealed class ModuleInfo : IConditional
{
///
public string Name { get; }
@@ -23,7 +27,7 @@ public sealed class Module : IConditionalComponent
///
/// The components of this module.
///
- public IConditionalComponent[] Components { get; }
+ public IConditional[] Components { get; }
///
/// Gets the type of this module.
@@ -36,9 +40,9 @@ public sealed class Module : IConditionalComponent
///
/// This property is if no root is specified for this module.
///
- public Module Root { get; }
+ public ModuleInfo Root { get; }
- internal Module(Type type, IDictionary typeReaders, Module root = null, string expectedName = null, string[] aliases = null)
+ internal ModuleInfo(Type type, IDictionary typeReaders, ModuleInfo root = null, string expectedName = null, string[] aliases = null)
{
var attributes = type.GetAttributes(true);
var preconditions = attributes.GetPreconditions();
diff --git a/src/CSF.Core/TypeReaders/BaseTypeReader.cs b/src/CSF.Core/TypeReaders/Impl/BaseTypeReader.cs
similarity index 97%
rename from src/CSF.Core/TypeReaders/BaseTypeReader.cs
rename to src/CSF.Core/TypeReaders/Impl/BaseTypeReader.cs
index 410ef7f..a1570b4 100644
--- a/src/CSF.Core/TypeReaders/BaseTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/Impl/BaseTypeReader.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using CSF.Reflection;
+
+namespace CSF.TypeReaders
{
internal class BaseTypeReader : TypeReader
{
@@ -6,7 +8,7 @@ internal class BaseTypeReader : TypeReader
private readonly static Lazy> _container = new(ValueGenerator);
- public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value)
{
var parser = _container.Value[Type] as Tpd;
diff --git a/src/CSF.Core/TypeReaders/ColorTypeReader.cs b/src/CSF.Core/TypeReaders/Impl/ColorTypeReader.cs
similarity index 94%
rename from src/CSF.Core/TypeReaders/ColorTypeReader.cs
rename to src/CSF.Core/TypeReaders/Impl/ColorTypeReader.cs
index 7c09f20..f53cbd2 100644
--- a/src/CSF.Core/TypeReaders/ColorTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/Impl/ColorTypeReader.cs
@@ -1,9 +1,10 @@
-using System.Drawing;
+using CSF.Reflection;
+using System.Drawing;
using System.Globalization;
using System.Reflection;
using System.Text;
-namespace CSF
+namespace CSF.TypeReaders
{
internal class ColorTypeReader : TypeReader
{
@@ -45,7 +46,7 @@ public ColorTypeReader()
_spacedColors = spacedNames;
}
- public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value)
{
if (int.TryParse(value.Replace("#", "").Replace("0x", ""), NumberStyles.HexNumber, null, out var hexNumber))
return ValueTask.FromResult(Success(Color.FromArgb(hexNumber)));
diff --git a/src/CSF.Core/TypeReaders/EnumTypeReader.cs b/src/CSF.Core/TypeReaders/Impl/EnumTypeReader.cs
similarity index 89%
rename from src/CSF.Core/TypeReaders/EnumTypeReader.cs
rename to src/CSF.Core/TypeReaders/Impl/EnumTypeReader.cs
index 8a881ab..3f1d258 100644
--- a/src/CSF.Core/TypeReaders/EnumTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/Impl/EnumTypeReader.cs
@@ -1,4 +1,6 @@
-namespace CSF
+using CSF.Reflection;
+
+namespace CSF.TypeReaders
{
internal class EnumTypeReader(Type targetEnumType) : TypeReader
{
@@ -6,7 +8,7 @@ internal class EnumTypeReader(Type targetEnumType) : TypeReader
public override Type Type { get; } = targetEnumType;
- public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value)
{
if (Enum.TryParse(Type, value, true, out var result))
return ValueTask.FromResult(Success(result));
diff --git a/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs b/src/CSF.Core/TypeReaders/Impl/TimeSpanTypeReader.cs
similarity index 94%
rename from src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs
rename to src/CSF.Core/TypeReaders/Impl/TimeSpanTypeReader.cs
index 4a018f5..8587351 100644
--- a/src/CSF.Core/TypeReaders/TimeSpanTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/Impl/TimeSpanTypeReader.cs
@@ -1,6 +1,7 @@
-using System.Text.RegularExpressions;
+using CSF.Reflection;
+using System.Text.RegularExpressions;
-namespace CSF
+namespace CSF.TypeReaders
{
internal partial class TimeSpanTypeReader : TypeReader
{
@@ -33,7 +34,7 @@ public TimeSpanTypeReader()
};
}
- public override ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value)
{
if (!TimeSpan.TryParse(value, out TimeSpan span))
{
diff --git a/src/CSF.Core/Abstractions/TypeReader.cs b/src/CSF.Core/TypeReaders/TypeReader.cs
similarity index 61%
rename from src/CSF.Core/Abstractions/TypeReader.cs
rename to src/CSF.Core/TypeReaders/TypeReader.cs
index 8b7cf28..98a0a03 100644
--- a/src/CSF.Core/Abstractions/TypeReader.cs
+++ b/src/CSF.Core/TypeReaders/TypeReader.cs
@@ -1,40 +1,28 @@
-using System.Diagnostics.CodeAnalysis;
+using CSF.Reflection;
+using CSF.Exceptions;
+using CSF.Helpers;
+using System.Diagnostics.CodeAnalysis;
-namespace CSF
+namespace CSF.TypeReaders
{
- ///
- /// Represents a generic to use for parsing provided types into the targetted type.
- ///
- /// The targetted type for this typereader.
public abstract class TypeReader : TypeReader
{
///
public override Type Type { get; } = typeof(T);
///
- public override abstract ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value);
+ public override abstract ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value);
}
public abstract class TypeReader
{
private static readonly string _exHeader = "TypeReader failed to parse provided value as '{0}'. View inner exception for more details.";
- ///
- /// The type that this reader intends to return.
- ///
public abstract Type Type { get; }
- ///
- /// Evaluates an input and tries to parse it into a value that matches the expected parameter type.
- ///
- /// The command context used to execute the command currently in scope.
- /// The parameter this input evaluation is targetting.
- /// The services in scope for the current command execution.
- /// The input that this evaluation intends to convert into the expected parameter type.
- /// A result that represents the outcome of the evaluation.
- public abstract ValueTask EvaluateAsync(ICommandContext context, IParameterComponent parameter, string value);
-
- internal ValueTask ObjectEvaluateAsync(ICommandContext context, IParameterComponent parameter, object value)
+ public abstract ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value);
+
+ internal ValueTask ObjectEvaluateAsync(ICommandContext context, IArgument parameter, object value)
{
if (value.GetType() == Type)
return ValueTask.FromResult(new ReadResult(value));
diff --git a/src/CSF.Tests.Console/TypeReaders/TimeOnlyTypeReader.cs b/src/CSF.Tests.Console/TypeReaders/TimeOnlyTypeReader.cs
deleted file mode 100644
index 2b6d1b0..0000000
--- a/src/CSF.Tests.Console/TypeReaders/TimeOnlyTypeReader.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace CSF.Tests
-{
- public class TimeOnlyTypeReader : TypeReader
- {
- public override Result Evaluate(ICommandContext context, IParameterComponent parameter, IServiceProvider services, string value)
- {
- return Success(TimeOnly.MinValue);
- }
- }
-}
From c415fcb2cb8335347245c27b8f42c567b2e83147 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Thu, 25 Jan 2024 22:15:59 +0100
Subject: [PATCH 07/40] new ignore
---
.gitignore | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 9491a2f..5fccc8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,4 +360,5 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
-FodyWeavers.xsd
\ No newline at end of file
+FodyWeavers.xsd
+/Visual Studio 2022/Visualizers
From 0ed35f3652bcb5a58f1ee861d07295856edc7ff3 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Sat, 27 Jan 2024 20:33:52 +0100
Subject: [PATCH 08/40] Introduce functional parsing, stringgen
---
.../CSF.Benchmark.Parsing.csproj | 18 ++++
CSF.Benchmark.Parsing/Program.cs | 22 +++++
CSF.sln | 24 ++---
src/CSF.Core/Parsing/Impl/CharSpanParser.cs | 16 ----
src/CSF.Core/Parsing/Impl/StringParser.cs | 93 +++++++++++++++++--
.../Parsing/Implementation/TextParser.cs | 85 -----------------
src/CSF.Core/Parsing/SpanParser.cs | 21 -----
src/CSF.Tests.Console/Program.cs | 38 +++++---
8 files changed, 161 insertions(+), 156 deletions(-)
create mode 100644 CSF.Benchmark.Parsing/CSF.Benchmark.Parsing.csproj
create mode 100644 CSF.Benchmark.Parsing/Program.cs
delete mode 100644 src/CSF.Core/Parsing/Impl/CharSpanParser.cs
delete mode 100644 src/CSF.Core/Parsing/Implementation/TextParser.cs
delete mode 100644 src/CSF.Core/Parsing/SpanParser.cs
diff --git a/CSF.Benchmark.Parsing/CSF.Benchmark.Parsing.csproj b/CSF.Benchmark.Parsing/CSF.Benchmark.Parsing.csproj
new file mode 100644
index 0000000..5508c60
--- /dev/null
+++ b/CSF.Benchmark.Parsing/CSF.Benchmark.Parsing.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CSF.Benchmark.Parsing/Program.cs b/CSF.Benchmark.Parsing/Program.cs
new file mode 100644
index 0000000..ed3ebae
--- /dev/null
+++ b/CSF.Benchmark.Parsing/Program.cs
@@ -0,0 +1,22 @@
+
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Running;
+using CSF.Parsing;
+
+[MemoryDiagnoser]
+public class Program
+{
+ private static readonly StringParser _parser = new();
+
+ [Params("command", "a larger command with context", "a massive command \"with quotes\" and several additional 1 22 333 4444 5555")]
+ public string Text { get; set; }
+
+ static void Main()
+ => BenchmarkRunner.Run();
+
+ [Benchmark]
+ public void ParseText()
+ {
+ _parser.Parse(Text);
+ }
+}
\ No newline at end of file
diff --git a/CSF.sln b/CSF.sln
index 996467e..56979a7 100644
--- a/CSF.sln
+++ b/CSF.sln
@@ -21,7 +21,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSF.Tests.Hosting", "src\CS
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSF.Samples.Hosting", "examples\CSF.Samples.Hosting\CSF.Samples.Hosting.csproj", "{D99DCD40-8A42-42F5-8385-88EDF87057CC}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Tests.Benchmarks", "src\CSF.Tests.Benchmarks\CSF.Tests.Benchmarks.csproj", "{0A3A4351-DD9A-4E25-9CA7-8CFFA5A3EE7B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSF.Tests.Benchmarks", "src\CSF.Tests.Benchmarks\CSF.Tests.Benchmarks.csproj", "{0A3A4351-DD9A-4E25-9CA7-8CFFA5A3EE7B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{7EB2ED0C-26A8-4F51-81CD-A0AB13B739C9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSF.Benchmark.Parsing", "CSF.Benchmark.Parsing\CSF.Benchmark.Parsing.csproj", "{6ABD1982-481C-4006-B830-997837E2B24D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -41,22 +45,10 @@ Global
{10058F39-52CB-44D6-AD8B-597F34B50B45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10058F39-52CB-44D6-AD8B-597F34B50B45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10058F39-52CB-44D6-AD8B-597F34B50B45}.Release|Any CPU.Build.0 = Release|Any CPU
- {B868198E-67C3-4AFB-8D19-48034E1156FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B868198E-67C3-4AFB-8D19-48034E1156FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B868198E-67C3-4AFB-8D19-48034E1156FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B868198E-67C3-4AFB-8D19-48034E1156FC}.Release|Any CPU.Build.0 = Release|Any CPU
{19F7A07B-7349-4AA5-A9CC-58F1002DFED6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19F7A07B-7349-4AA5-A9CC-58F1002DFED6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19F7A07B-7349-4AA5-A9CC-58F1002DFED6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19F7A07B-7349-4AA5-A9CC-58F1002DFED6}.Release|Any CPU.Build.0 = Release|Any CPU
- {CA734A9C-2E3A-4E61-8CD0-0476AD88F94C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CA734A9C-2E3A-4E61-8CD0-0476AD88F94C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CA734A9C-2E3A-4E61-8CD0-0476AD88F94C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CA734A9C-2E3A-4E61-8CD0-0476AD88F94C}.Release|Any CPU.Build.0 = Release|Any CPU
- {924925E1-0AE5-4B5A-B736-EB09EF5BDB60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {924925E1-0AE5-4B5A-B736-EB09EF5BDB60}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {924925E1-0AE5-4B5A-B736-EB09EF5BDB60}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {924925E1-0AE5-4B5A-B736-EB09EF5BDB60}.Release|Any CPU.Build.0 = Release|Any CPU
{BD637AD8-B1B6-4C4E-9C28-FEF0B2824B20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD637AD8-B1B6-4C4E-9C28-FEF0B2824B20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD637AD8-B1B6-4C4E-9C28-FEF0B2824B20}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -73,6 +65,10 @@ Global
{0A3A4351-DD9A-4E25-9CA7-8CFFA5A3EE7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A3A4351-DD9A-4E25-9CA7-8CFFA5A3EE7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A3A4351-DD9A-4E25-9CA7-8CFFA5A3EE7B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6ABD1982-481C-4006-B830-997837E2B24D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6ABD1982-481C-4006-B830-997837E2B24D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6ABD1982-481C-4006-B830-997837E2B24D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6ABD1982-481C-4006-B830-997837E2B24D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -80,11 +76,11 @@ Global
GlobalSection(NestedProjects) = preSolution
{A70E8C5D-0209-433B-9AB7-9C05C0DA04E3} = {DA6771C2-541A-46E0-AF1A-B4256FF4CB5E}
{10058F39-52CB-44D6-AD8B-597F34B50B45} = {DA6771C2-541A-46E0-AF1A-B4256FF4CB5E}
- {B868198E-67C3-4AFB-8D19-48034E1156FC} = {01CCF11A-2D95-44C9-81EA-EE7D6A36FE7A}
{19F7A07B-7349-4AA5-A9CC-58F1002DFED6} = {01CCF11A-2D95-44C9-81EA-EE7D6A36FE7A}
{DC2807A7-F45F-422F-A9CB-75E1CC0A9978} = {DA6771C2-541A-46E0-AF1A-B4256FF4CB5E}
{D99DCD40-8A42-42F5-8385-88EDF87057CC} = {01CCF11A-2D95-44C9-81EA-EE7D6A36FE7A}
{0A3A4351-DD9A-4E25-9CA7-8CFFA5A3EE7B} = {DA6771C2-541A-46E0-AF1A-B4256FF4CB5E}
+ {6ABD1982-481C-4006-B830-997837E2B24D} = {7EB2ED0C-26A8-4F51-81CD-A0AB13B739C9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C9CE5996-C6A9-442C-8808-490D2FA61458}
diff --git a/src/CSF.Core/Parsing/Impl/CharSpanParser.cs b/src/CSF.Core/Parsing/Impl/CharSpanParser.cs
deleted file mode 100644
index 869679c..0000000
--- a/src/CSF.Core/Parsing/Impl/CharSpanParser.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF.Parsing
-{
- public sealed class CharSpanParser : SpanParser
- {
- public override object[] Parse(Span value)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/src/CSF.Core/Parsing/Impl/StringParser.cs b/src/CSF.Core/Parsing/Impl/StringParser.cs
index df52e51..b5b4bbc 100644
--- a/src/CSF.Core/Parsing/Impl/StringParser.cs
+++ b/src/CSF.Core/Parsing/Impl/StringParser.cs
@@ -1,18 +1,95 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Text;
namespace CSF.Parsing
{
public class StringParser : Parser
{
- private static readonly SpanParser _spanParser = new CharSpanParser();
+ const char quote = '"';
+ const char whitespace = ' ';
- public override object[] Parse(string value)
+ public override object[] Parse(string toParse)
{
-
+ var arr = Array.Empty();
+ var sb = new StringBuilder(0, toParse.Length);
+ var quoted = false;
+
+ // Adds SB content to array & resets
+ void SAddReset()
+ {
+ // if anything exists, otherwise skip
+ if (sb.Length > 0)
+ {
+ var size = arr.Length;
+ Array.Resize(ref arr, size + 1);
+
+ arr[size] = sb.ToString();
+
+ // clear for next range
+ sb.Clear();
+ }
+ }
+
+ // enter loop for string inner char[]
+ for (int i = 0; i < toParse.Length; i++)
+ {
+ // if startquote found, skip space check & continue until next occurrence of quote
+ if (quoted)
+ {
+ // next quote occurrence
+ if (toParse[i] is quote)
+ {
+ // add discovered until now, skipping quote itself
+ SAddReset();
+
+ // set quoted to false, quoted range is handled
+ quoted = false;
+
+ // next loop step
+ continue;
+ }
+
+ // add char in quote range
+ sb.Append(toParse[i]);
+
+ // dont allow the checks below this statement, next loop step
+ continue;
+ }
+
+ // check for startquote
+ if (toParse[i] is quote)
+ {
+ // check end of loop, skipping add
+ if (i + 1 == toParse.Length)
+ {
+ break;
+ }
+
+ // add all before quote
+ SAddReset();
+
+ // set startquote discovery to true
+ quoted = true;
+
+ continue;
+ }
+
+ // check for whitespace
+ if (toParse[i] is whitespace)
+ {
+ // add all before whitespace, skip whitespace itself
+ SAddReset();
+
+ continue;
+ }
+
+ // nomatch for above, add character to current range
+ sb.Append(toParse[i]);
+ }
+
+ // if loop ended, do final add
+ SAddReset();
+
+ return arr;
}
}
}
diff --git a/src/CSF.Core/Parsing/Implementation/TextParser.cs b/src/CSF.Core/Parsing/Implementation/TextParser.cs
deleted file mode 100644
index 6087543..0000000
--- a/src/CSF.Core/Parsing/Implementation/TextParser.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-namespace CSF
-{
- ///
- /// Represents a parser for text command input.
- ///
- public class TextParser : Parser
- {
- public override ParserCell Parse(string rawInput)
- {
- var splitInput = rawInput.Split(' ');
-
- var hasName = false;
-
- var param = new List();
- var partial = new List();
-
- var paramName = "";
- var namedParam = new Dictionary();
-
- foreach (var part in splitInput)
- {
- if (!hasName)
- {
- param.Add(part);
- continue;
- }
-
- if (partial.Any())
- {
- if (part.EndsWith("\""))
- {
- partial.Add(part.Replace("\"", ""));
-
- if (paramName is "")
- param.Add(string.Join(" ", partial));
- else
- {
- namedParam.Add(paramName, string.Join(" ", partial));
- paramName = "";
- }
-
- partial.Clear();
- continue;
- }
- partial.Add(part);
- continue;
- }
-
- if (part.StartsWith('"'))
- {
- if (part.EndsWith('"'))
- {
- if (paramName is "")
- param.Add(part.Replace("\"", ""));
- else
- {
- namedParam.Add(paramName, part.Replace("\"", ""));
- paramName = "";
- }
- }
- else
- partial.Add(part.Replace("\"", ""));
- continue;
- }
-
- if (part.StartsWith("-"))
- foreach (var c in part[1..])
- namedParam.Add(c.ToString(), null);
-
- if (part.StartsWith("--"))
- {
- if (!part.EndsWith(":"))
- namedParam.Add(part[1..], null!);
- else
- paramName = part[1..^1];
- continue;
- }
-
- param.Add(part);
- }
-
- return new(param.ToArray(), namedParam);
- }
- }
-}
diff --git a/src/CSF.Core/Parsing/SpanParser.cs b/src/CSF.Core/Parsing/SpanParser.cs
deleted file mode 100644
index 239209e..0000000
--- a/src/CSF.Core/Parsing/SpanParser.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF.Parsing
-{
- public abstract class SpanParser : Parser
- where T : struct, IEquatable
- {
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override object[] Parse(T value)
- {
- throw new NotImplementedException();
- }
-
- public abstract object[] Parse(Span value);
- }
-}
diff --git a/src/CSF.Tests.Console/Program.cs b/src/CSF.Tests.Console/Program.cs
index 601ec70..68ba3ad 100644
--- a/src/CSF.Tests.Console/Program.cs
+++ b/src/CSF.Tests.Console/Program.cs
@@ -1,20 +1,20 @@
-using CSF;
-using Microsoft.Extensions.DependencyInjection;
+//using CSF;
+//using Microsoft.Extensions.DependencyInjection;
-var collection = new ServiceCollection()
- .WithCommandManager();
+//var collection = new ServiceCollection()
+// .WithCommandManager();
-var services = collection.BuildServiceProvider();
+//var services = collection.BuildServiceProvider();
-var framework = services.GetRequiredService();
+//var framework = services.GetRequiredService();
-var delayed = new CommandContext("delayed");
-var direct = new CommandContext("direct");
+//var delayed = new CommandContext("delayed");
+//var direct = new CommandContext("direct");
-framework.ExecuteAsync(delayed);
-framework.ExecuteAsync(direct);
+//framework.ExecuteAsync(delayed);
+//framework.ExecuteAsync(direct);
-await Task.Delay(Timeout.Infinite);
+//await Task.Delay(Timeout.Infinite);
//while (true)
//{
@@ -24,4 +24,18 @@
// if (result.Failed(out var failure))
// Console.WriteLine(failure.Exception);
-//}
\ No newline at end of file
+//}
+
+using CSF.Parsing;
+
+var parser = new StringParser();
+
+while (true)
+{
+ var text = Console.ReadLine();
+
+ var value = parser.Parse(text);
+
+ foreach (var item in value)
+ Console.WriteLine("-> " + item);
+}
\ No newline at end of file
From ed71ed813788fb5f943a0bf35ac150d7fc06e69f Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Sat, 27 Jan 2024 22:37:57 +0100
Subject: [PATCH 09/40] Upgrades to hosting extensions, logging support
---
src/CSF.Core/Core/CommandManager.cs | 39 +++++------
src/CSF.Core/Core/CommandManagerResolver.cs | 41 ++++++++++++
.../Core/Execution/ICommandContext.cs | 11 ++++
.../Core/Execution/Impl/CommandContext.cs | 34 +++++++++-
src/CSF.Core/Core/Execution/ModuleBase.cs | 47 +++++--------
src/CSF.Core/Exceptions/MatchException.cs | 2 +-
src/CSF.Core/Helpers/ServiceHelpers.cs | 10 ---
.../Preconditions/PreconditionAttribute.cs | 10 +--
src/CSF.Core/Reflection/IArgument.cs | 28 ++------
src/CSF.Core/Reflection/IArgumentBucket.cs | 20 ++----
src/CSF.Core/Reflection/IConditional.cs | 16 ++---
src/CSF.Core/Reflection/INameable.cs | 12 +---
src/CSF.Core/Reflection/Impl/ArgumentInfo.cs | 16 +----
src/CSF.Core/Reflection/Impl/CommandInfo.cs | 41 +++++-------
.../Reflection/Impl/ComplexArgumentInfo.cs | 37 +++++------
src/CSF.Core/Reflection/Impl/ModuleInfo.cs | 34 +++-------
src/CSF.Core/TypeReaders/TypeReader.cs | 3 +-
.../Core/Execution/IContextFactory.cs | 24 +++++++
.../Execution/Impl/HostedCommandContext.cs | 55 ++++++++++++++++
.../Execution/Impl/HostedContextFactory.cs | 34 ++++++++++
.../Core/Execution/Impl/HostedModuleBase.cs | 19 ++++++
src/CSF.Hosting/Core/HostedCommandManager.cs | 31 +++++++++
.../Core/HostedCommandManagerResolver.cs | 44 +++++++++++++
src/CSF.Hosting/Helpers/HostBuilderHelper.cs | 48 --------------
src/CSF.Hosting/Helpers/ServiceHelpers.cs | 12 ++++
src/CSF.Hosting/HostedCommandManager.cs | 66 -------------------
26 files changed, 399 insertions(+), 335 deletions(-)
create mode 100644 src/CSF.Core/Core/CommandManagerResolver.cs
create mode 100644 src/CSF.Hosting/Core/Execution/IContextFactory.cs
create mode 100644 src/CSF.Hosting/Core/Execution/Impl/HostedCommandContext.cs
create mode 100644 src/CSF.Hosting/Core/Execution/Impl/HostedContextFactory.cs
create mode 100644 src/CSF.Hosting/Core/Execution/Impl/HostedModuleBase.cs
create mode 100644 src/CSF.Hosting/Core/HostedCommandManager.cs
create mode 100644 src/CSF.Hosting/Core/HostedCommandManagerResolver.cs
delete mode 100644 src/CSF.Hosting/Helpers/HostBuilderHelper.cs
create mode 100644 src/CSF.Hosting/Helpers/ServiceHelpers.cs
delete mode 100644 src/CSF.Hosting/HostedCommandManager.cs
diff --git a/src/CSF.Core/Core/CommandManager.cs b/src/CSF.Core/Core/CommandManager.cs
index 14ab88f..70f5908 100644
--- a/src/CSF.Core/Core/CommandManager.cs
+++ b/src/CSF.Core/Core/CommandManager.cs
@@ -1,5 +1,4 @@
-using Microsoft.Extensions.DependencyInjection;
-using CSF.TypeReaders;
+using CSF.TypeReaders;
using CSF.Helpers;
using CSF.Reflection;
using CSF.Exceptions;
@@ -8,7 +7,7 @@
namespace CSF
{
- public sealed class CommandManager
+ public class CommandManager
{
public IServiceProvider Services { get; }
@@ -36,7 +35,7 @@ public CommandManager(IServiceProvider services, CommandConfiguration configurat
Configuration = configuration;
}
- public async ValueTask ExecuteAsync(ICommandContext context, params object[] args)
+ public virtual async Task ExecuteAsync(ICommandContext context, params object[] args)
{
// search all relevant commands.
var searches = Search(args);
@@ -57,26 +56,26 @@ public async ValueTask ExecuteAsync(ICommandContext context, params obj
}
if (!fallback.HasValue)
- return new SearchResult(new SearchException("")); // TODO
+ return new SearchResult(new SearchException("No command was found with the provided input."));
return fallback;
}
- public IEnumerable Search(object[] args)
+ public virtual IEnumerable Search(object[] args)
{
// recursively search for commands in the execution.
return Components.RecursiveSearch(args, 0);
}
#region Matching
- private static async ValueTask MatchAsync(ICommandContext context, SearchResult search, object[] args)
+ private async ValueTask MatchAsync(ICommandContext context, SearchResult search, object[] args)
{
// check command preconditions.
var check = await CheckAsync(context, search.Command);
// verify check success, if not, return the failure.
if (!check.Success)
- return new(search.Command, new MatchException("", check.Exception)); // TODO
+ return new(search.Command, new MatchException("Command failed to reach execution state. View inner exception for more details.", check.Exception));
// read the command parameters in right order.
var readResult = await ReadAsync(context, search, args);
@@ -98,8 +97,10 @@ private static async ValueTask MatchAsync(ICommandContext context,
#endregion
#region Reading
- private static async ValueTask ReadAsync(ICommandContext context, SearchResult search, object[] args)
+ private async ValueTask ReadAsync(ICommandContext context, SearchResult search, object[] args)
{
+ context.LogDebug("Attempting argument conversion for {}", search.Command);
+
// skip if no parameters exist.
if (!search.Command.HasParameters)
return [];
@@ -125,8 +126,10 @@ private static async ValueTask ReadAsync(ICommandContext context,
#endregion
#region Checking
- private static async ValueTask CheckAsync(ICommandContext context, CommandInfo command)
+ private async ValueTask CheckAsync(ICommandContext context, CommandInfo command)
{
+ context.LogDebug("Attempting validations for {}", command);
+
foreach (var precon in command.Preconditions)
{
var result = await precon.EvaluateAsync(context, command);
@@ -143,6 +146,8 @@ private async ValueTask RunAsync(ICommandContext context, MatchResult
{
try
{
+ context.LogInformation("Executing {} with {} resolved arguments.", match.Command, match.Reads.Length);
+
var module = Services.GetService(match.Command.Module.Type) as ModuleBase;
module.Context = context;
@@ -185,18 +190,4 @@ private IEnumerable BuildComponents(CommandConfiguration configurati
}
#endregion
}
-
- public static class CollectionExtensions
- {
- public static IServiceCollection WithCommandManager(this IServiceCollection collection, Action action = null)
- {
- var context = new CommandConfiguration();
-
- action?.Invoke(context);
-
- collection.AddCommandManager(context);
-
- return collection;
- }
- }
}
diff --git a/src/CSF.Core/Core/CommandManagerResolver.cs b/src/CSF.Core/Core/CommandManagerResolver.cs
new file mode 100644
index 0000000..a17f189
--- /dev/null
+++ b/src/CSF.Core/Core/CommandManagerResolver.cs
@@ -0,0 +1,41 @@
+using CSF.Helpers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+
+namespace CSF.Core
+{
+ public static class CommandManagerResolver
+ {
+ public static IServiceCollection WithCommands(this IServiceCollection collection, [DisallowNull] Action action)
+ {
+ collection.WithCommands(action);
+
+ return collection;
+ }
+
+ public static IServiceCollection WithCommands(this IServiceCollection collection, [DisallowNull] Action action)
+ where T : CommandManager
+ {
+ var cmdConf = new CommandConfiguration();
+
+ action(cmdConf);
+
+ collection.WithCommands(cmdConf);
+
+ return collection;
+ }
+
+ public static IServiceCollection WithCommands(this IServiceCollection collection, CommandConfiguration configuration)
+ where T : CommandManager
+ {
+ collection.ModulesAddTransient(configuration);
+
+ collection.TryAddSingleton(configuration);
+ collection.TryAddSingleton();
+
+ return collection;
+ }
+ }
+}
diff --git a/src/CSF.Core/Core/Execution/ICommandContext.cs b/src/CSF.Core/Core/Execution/ICommandContext.cs
index b02d152..0bed802 100644
--- a/src/CSF.Core/Core/Execution/ICommandContext.cs
+++ b/src/CSF.Core/Core/Execution/ICommandContext.cs
@@ -4,5 +4,16 @@ namespace CSF
{
public interface ICommandContext
{
+ public void LogTrace(string message, params object[] args);
+
+ public void LogDebug(string message, params object[] args);
+
+ public void LogInformation(string message, params object[] args);
+
+ public void LogWarning(string message, params object[] args);
+
+ public void LogError(string message, params object[] args);
+
+ public void LogCritical(string message, params object[] args);
}
}
diff --git a/src/CSF.Core/Core/Execution/Impl/CommandContext.cs b/src/CSF.Core/Core/Execution/Impl/CommandContext.cs
index e0b04e7..6ba3685 100644
--- a/src/CSF.Core/Core/Execution/Impl/CommandContext.cs
+++ b/src/CSF.Core/Core/Execution/Impl/CommandContext.cs
@@ -1,7 +1,37 @@
-namespace CSF
+using CSF.Reflection;
+
+namespace CSF
{
- public class CommandContext : ICommandContext
+ public abstract class CommandContext : ICommandContext
{
+ public virtual void LogCritical(string message, params object[] args)
+ {
+
+ }
+
+ public virtual void LogDebug(string message, params object[] args)
+ {
+
+ }
+
+ public virtual void LogError(string message, params object[] args)
+ {
+
+ }
+
+ public virtual void LogInformation(string message, params object[] args)
+ {
+
+ }
+
+ public virtual void LogTrace(string message, params object[] args)
+ {
+
+ }
+ public virtual void LogWarning(string message, params object[] args)
+ {
+
+ }
}
}
diff --git a/src/CSF.Core/Core/Execution/ModuleBase.cs b/src/CSF.Core/Core/Execution/ModuleBase.cs
index 2640f64..ce96cd8 100644
--- a/src/CSF.Core/Core/Execution/ModuleBase.cs
+++ b/src/CSF.Core/Core/Execution/ModuleBase.cs
@@ -2,18 +2,11 @@
namespace CSF
{
- ///
- /// Represents a registration and execution tool for modules.
- ///
- /// The expected to use for this module.
public abstract class ModuleBase : ModuleBase
where T : ICommandContext
{
private T _context;
- ///
- /// Gets the command execution context containing value about the currently executed command.
- ///
public new T Context
{
get
@@ -21,46 +14,42 @@ public abstract class ModuleBase : ModuleBase
}
}
- ///
- /// Represents a registration and execution tool for modules.
- ///
public abstract class ModuleBase
{
- ///
- /// Gets the command execution context containing value about the currently executed command.
- ///
public ICommandContext Context { get; internal set; }
- ///
- /// Gets the command execution services as provided by the command scope.
- ///
public IServiceProvider Services { get; internal set; }
- ///
- /// Gets the component that displays all information about the command thats currently in scope.
- ///
public CommandInfo Command { get; internal set; }
- ///
- /// Responds to the command with a message.
- ///
- /// The message to send.
- public virtual void Respond(string message)
- => Console.WriteLine(message);
-
- public virtual RunResult ReturnTypeResolve(object value)
+ internal virtual RunResult ReturnTypeResolve(object value)
{
switch (value)
{
case Task task:
return new(Command, task);
case null:
- return new(Command, null);
+ return new(Command, returnValue: null);
default:
- throw new NotSupportedException($"The return value of the command in question is not supported. Consider overriding {nameof(ReturnTypeResolve)} to add your own return type resolver.");
+ {
+ var result = HandleUnknownReturnType(value);
+
+ if (!result.Success)
+ {
+ Context.LogWarning("{} returned unknown type. Consider overriding {} to resolve this message.", Command, nameof(HandleUnknownReturnType));
+ return new(Command, returnValue: value);
+ }
+
+ return result;
+ }
}
}
+ public virtual RunResult HandleUnknownReturnType(object value)
+ {
+ return new RunResult(Command, exception: null);
+ }
+
public virtual ValueTask BeforeExecuteAsync()
{
return ValueTask.CompletedTask;
diff --git a/src/CSF.Core/Exceptions/MatchException.cs b/src/CSF.Core/Exceptions/MatchException.cs
index 6dd0fae..cea2c86 100644
--- a/src/CSF.Core/Exceptions/MatchException.cs
+++ b/src/CSF.Core/Exceptions/MatchException.cs
@@ -3,6 +3,6 @@
public class MatchException(string message, Exception innerException = null)
: ExecutionException(message, innerException)
{
-
+ private const string _exHeader = "Command failed to reach execution state. View inner exception for more details.";
}
}
diff --git a/src/CSF.Core/Helpers/ServiceHelpers.cs b/src/CSF.Core/Helpers/ServiceHelpers.cs
index b928f64..c1c0cdb 100644
--- a/src/CSF.Core/Helpers/ServiceHelpers.cs
+++ b/src/CSF.Core/Helpers/ServiceHelpers.cs
@@ -5,16 +5,6 @@ namespace CSF.Helpers
{
internal static class ServiceHelpers
{
- public static IServiceCollection AddCommandManager(this IServiceCollection collection, CommandConfiguration configuration)
- {
- collection.ModulesAddTransient(configuration);
-
- collection.TryAddSingleton(configuration);
- collection.TryAddSingleton();
-
- return collection;
- }
-
public static IServiceCollection ModulesAddTransient(this IServiceCollection collection, CommandConfiguration configuration)
{
var rootType = typeof(ModuleBase);
diff --git a/src/CSF.Core/Preconditions/PreconditionAttribute.cs b/src/CSF.Core/Preconditions/PreconditionAttribute.cs
index b2c48e3..0ea3453 100644
--- a/src/CSF.Core/Preconditions/PreconditionAttribute.cs
+++ b/src/CSF.Core/Preconditions/PreconditionAttribute.cs
@@ -5,19 +5,11 @@
namespace CSF.Preconditions
{
- ///
- /// Defines a precondition attribute.
- ///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public abstract class PreconditionAttribute : Attribute
{
private static readonly string _exHeader = "Precondition result halted further command execution. View inner exception for more details.";
- ///
- /// Evaluates a condition to handle a command and returns the result.
- ///
- /// The command context used to execute the command currently in scope.
- /// The command that is to be executed if this and all other precondition evaluations succeed.
- /// A result that represents the outcome of the evaluation.
+
public abstract ValueTask EvaluateAsync(ICommandContext context, CommandInfo command);
public static CheckResult Error([DisallowNull] Exception exception)
diff --git a/src/CSF.Core/Reflection/IArgument.cs b/src/CSF.Core/Reflection/IArgument.cs
index da78714..d264599 100644
--- a/src/CSF.Core/Reflection/IArgument.cs
+++ b/src/CSF.Core/Reflection/IArgument.cs
@@ -2,39 +2,25 @@
namespace CSF.Reflection
{
- ///
- /// Represents a constructor or method parameter.
- ///
+
public interface IArgument : INameable
{
- ///
- /// Gets the type of the parameter.
- ///
+
public Type Type { get; }
- ///
- /// Gets the type that is exposed to the runtime directly, potentially being nullable.
- ///
+
public Type ExposedType { get; }
- ///
- /// Gets if the parameter is nullable.
- ///
+
public bool IsNullable { get; }
- ///
- /// Gets if the parameter is optional.
- ///
+
public bool IsOptional { get; }
- ///
- /// Gets if the parameter is remainder.
- ///
+
public bool IsRemainder { get; }
- ///
- /// Gets the typereader responsible for parsing this type.
- ///
+
public TypeReader TypeReader { get; }
}
}
diff --git a/src/CSF.Core/Reflection/IArgumentBucket.cs b/src/CSF.Core/Reflection/IArgumentBucket.cs
index 87d9c46..0662874 100644
--- a/src/CSF.Core/Reflection/IArgumentBucket.cs
+++ b/src/CSF.Core/Reflection/IArgumentBucket.cs
@@ -1,28 +1,18 @@
namespace CSF.Reflection
{
- ///
- /// Represents a container that holds and handles parameters.
- ///
+
public interface IArgumentBucket
{
- ///
- /// Gets a list of parameters for this container.
- ///
+
public IArgument[] Parameters { get; }
- ///
- /// Gets if this container contains any parameters or not.
- ///
+
public bool HasParameters { get; }
- ///
- /// Gets the minimum required length to use a command.
- ///
+
public int MinLength { get; }
- ///
- /// Gets the optimal length to use a command. If remainder is specified, the count will be set to infinity.
- ///
+
public int MaxLength { get; }
}
}
diff --git a/src/CSF.Core/Reflection/IConditional.cs b/src/CSF.Core/Reflection/IConditional.cs
index 4171dcd..1c9ac1e 100644
--- a/src/CSF.Core/Reflection/IConditional.cs
+++ b/src/CSF.Core/Reflection/IConditional.cs
@@ -2,24 +2,16 @@
namespace CSF.Reflection
{
- ///
- /// Represents a component with preconditions available.
- ///
+
public interface IConditional : INameable
{
- ///
- /// Gets the aliases of this component.
- ///
+
public string[] Aliases { get; }
- ///
- /// Gets the preconditions of this component.
- ///
+
public PreconditionAttribute[] Preconditions { get; }
- ///
- /// Gets if this component has any preconditions.
- ///
+
public bool HasPreconditions { get; }
}
}
diff --git a/src/CSF.Core/Reflection/INameable.cs b/src/CSF.Core/Reflection/INameable.cs
index dfee656..5d46b9a 100644
--- a/src/CSF.Core/Reflection/INameable.cs
+++ b/src/CSF.Core/Reflection/INameable.cs
@@ -1,18 +1,12 @@
namespace CSF.Reflection
{
- ///
- /// Represents any part of a command.
- ///
+
public interface INameable
{
- ///
- /// Gets the name of the component in question.
- ///
+
public string Name { get; }
- ///
- /// Gets the attribute collection for this component.
- ///
+
public Attribute[] Attributes { get; }
}
}
diff --git a/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs b/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
index 10ab12a..cc41ec9 100644
--- a/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
+++ b/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
@@ -4,33 +4,23 @@
namespace CSF.Reflection
{
- ///
- /// Represents a single parameter for the method.
- ///
+
public sealed class ArgumentInfo : IArgument
{
- ///
public string Name { get; }
- ///
public Type Type { get; }
- ///
public Type ExposedType { get; }
- ///
public bool IsNullable { get; }
- ///
public bool IsOptional { get; }
- ///
public bool IsRemainder { get; }
- ///
public Attribute[] Attributes { get; }
- ///
public TypeReader TypeReader { get; }
internal ArgumentInfo(ParameterInfo parameterInfo, IDictionary typeReaders)
@@ -70,10 +60,6 @@ internal ArgumentInfo(ParameterInfo parameterInfo, IDictionary
Name = parameterInfo.Name;
}
- ///
- /// Formats the type into a readable signature.
- ///
- /// A string containing a readable signature.
public override string ToString()
=> $"{Type.Name} {Name}";
}
diff --git a/src/CSF.Core/Reflection/Impl/CommandInfo.cs b/src/CSF.Core/Reflection/Impl/CommandInfo.cs
index 40cee11..a27207a 100644
--- a/src/CSF.Core/Reflection/Impl/CommandInfo.cs
+++ b/src/CSF.Core/Reflection/Impl/CommandInfo.cs
@@ -5,54 +5,46 @@
namespace CSF.Reflection
{
- ///
- /// Represents the information required to execute commands.
- ///
+
public sealed class CommandInfo : IConditional, IArgumentBucket
{
- ///
+
public string Name { get; }
- ///
+
public Attribute[] Attributes { get; }
- ///
+
public PreconditionAttribute[] Preconditions { get; }
- ///
+
public bool HasPreconditions { get; }
- ///
+
public IArgument[] Parameters { get; }
- ///
+
public bool HasParameters { get; }
- ///
+
public bool HasRemainder { get; }
- ///
+
public int MinLength { get; }
- ///
+
public int MaxLength { get; }
- ///
+
public string[] Aliases { get; }
- ///
- /// Represents the priority of a command.
- ///
+
public byte Priority { get; }
- ///
- /// Gets the module this command is declared in.
- ///
+
public ModuleInfo Module { get; }
- ///
- /// Gets the target method this command is aimed to execute.
- ///
+
public MethodInfo Target { get; }
internal CommandInfo(ModuleInfo module, MethodInfo method, string[] aliases, IDictionary typeReaders)
@@ -91,10 +83,7 @@ internal CommandInfo(ModuleInfo module, MethodInfo method, string[] aliases, IDi
MaxLength = maxLength;
}
- ///
- /// Formats the type into a readable signature.
- ///
- /// A string containing a readable signature.
+
public override string ToString()
=> $"{Module}.{Target.Name}['{Name}']({string.Join(", ", Parameters)})";
}
diff --git a/src/CSF.Core/Reflection/Impl/ComplexArgumentInfo.cs b/src/CSF.Core/Reflection/Impl/ComplexArgumentInfo.cs
index ed70bff..2c2e3f6 100644
--- a/src/CSF.Core/Reflection/Impl/ComplexArgumentInfo.cs
+++ b/src/CSF.Core/Reflection/Impl/ComplexArgumentInfo.cs
@@ -4,50 +4,46 @@
namespace CSF.Reflection
{
- ///
- /// Represents a complex parameter, containing a number of its own parameters.
- ///
+
public class ComplexArgumentInfo : IArgument, IArgumentBucket
{
- ///
+
public string Name { get; }
- ///
+
public Type Type { get; }
- ///
+
public Type ExposedType { get; }
- ///
+
public bool IsNullable { get; }
- ///
+
public bool IsOptional { get; }
- ///
+
public bool IsRemainder { get; }
- ///
+
public Attribute[] Attributes { get; }
- ///
+
public IArgument[] Parameters { get; }
- ///
+
public bool HasParameters { get; }
- ///
+
public int MinLength { get; }
- ///
+
public int MaxLength { get; }
- ///
+
public TypeReader TypeReader { get; }
- ///
- /// Gets the constructor that constructs this complex parameter.
- ///
+
public ConstructorInfo Constructor { get; }
internal ComplexArgumentInfo(ParameterInfo parameterInfo, IDictionary typeReaders)
@@ -97,10 +93,7 @@ internal ComplexArgumentInfo(ParameterInfo parameterInfo, IDictionary
- /// Formats the type into a readable signature.
- ///
- /// A string containing a readable signature.
+
public override string ToString()
=> $"{Type.Name} ({string.Join(", ", Parameters)}) {Name}";
}
diff --git a/src/CSF.Core/Reflection/Impl/ModuleInfo.cs b/src/CSF.Core/Reflection/Impl/ModuleInfo.cs
index b8ccde2..723ac77 100644
--- a/src/CSF.Core/Reflection/Impl/ModuleInfo.cs
+++ b/src/CSF.Core/Reflection/Impl/ModuleInfo.cs
@@ -4,42 +4,31 @@
namespace CSF.Reflection
{
- ///
- /// Represents information about the module this command is executed in.
- ///
+
public sealed class ModuleInfo : IConditional
{
- ///
+
public string Name { get; }
- ///
+
public string[] Aliases { get; }
- ///
+
public Attribute[] Attributes { get; }
- ///
+
public PreconditionAttribute[] Preconditions { get; }
- ///
+
public bool HasPreconditions { get; }
- ///
- /// The components of this module.
- ///
+
public IConditional[] Components { get; }
- ///
- /// Gets the type of this module.
- ///
+
public Type Type { get; }
- ///
- /// Gets the root module of the current module.
- ///
- ///
- /// This property is if no root is specified for this module.
- ///
+
public ModuleInfo Root { get; }
internal ModuleInfo(Type type, IDictionary typeReaders, ModuleInfo root = null, string expectedName = null, string[] aliases = null)
@@ -60,10 +49,7 @@ internal ModuleInfo(Type type, IDictionary typeReaders, Module
Aliases = aliases ?? [Name];
}
- ///
- /// Formats the type into a readable signature.
- ///
- /// A string containing a readable signature.
+
public override string ToString()
=> $"{(Root != null ? $"{Root}." : "")}{(Type.Name != Name ? $"{Type.Name}['{Name}']" : $"{Name}")}";
}
diff --git a/src/CSF.Core/TypeReaders/TypeReader.cs b/src/CSF.Core/TypeReaders/TypeReader.cs
index 98a0a03..951d76f 100644
--- a/src/CSF.Core/TypeReaders/TypeReader.cs
+++ b/src/CSF.Core/TypeReaders/TypeReader.cs
@@ -2,15 +2,14 @@
using CSF.Exceptions;
using CSF.Helpers;
using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics;
namespace CSF.TypeReaders
{
public abstract class TypeReader : TypeReader
{
- ///
public override Type Type { get; } = typeof(T);
- ///
public override abstract ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value);
}
diff --git a/src/CSF.Hosting/Core/Execution/IContextFactory.cs b/src/CSF.Hosting/Core/Execution/IContextFactory.cs
new file mode 100644
index 0000000..5d0a360
--- /dev/null
+++ b/src/CSF.Hosting/Core/Execution/IContextFactory.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Hosting
+{
+ public interface IContextFactory : IDisposable
+ {
+ public ICommandContext CreateContext();
+ }
+
+ public interface IContextFactory : IContextFactory
+ where T : ICommandContext
+ {
+ public new T CreateContext();
+
+ ICommandContext IContextFactory.CreateContext()
+ {
+ return CreateContext();
+ }
+ }
+}
diff --git a/src/CSF.Hosting/Core/Execution/Impl/HostedCommandContext.cs b/src/CSF.Hosting/Core/Execution/Impl/HostedCommandContext.cs
new file mode 100644
index 0000000..5f6d458
--- /dev/null
+++ b/src/CSF.Hosting/Core/Execution/Impl/HostedCommandContext.cs
@@ -0,0 +1,55 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Hosting
+{
+ public class HostedCommandContext : ICommandContext
+ {
+ internal Guid Id { get; }
+
+ public ILogger Logger { get; }
+
+ public HostedCommandContext(Guid id, ILogger logger)
+ {
+ Id = id;
+ Logger = logger;
+ }
+
+ public void LogTrace(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Trace, message, args);
+ }
+
+ public void LogDebug(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Debug, message, args);
+ }
+
+ public void LogInformation(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Information, message, args);
+ }
+
+ public void LogWarning(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Warning, message, args);
+ }
+
+ public void LogError(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Error, message, args);
+ }
+
+ public void LogCritical(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Critical, message, args);
+ }
+
+ public override string ToString()
+ => $"HostedCommandContext[{Id}]";
+ }
+}
diff --git a/src/CSF.Hosting/Core/Execution/Impl/HostedContextFactory.cs b/src/CSF.Hosting/Core/Execution/Impl/HostedContextFactory.cs
new file mode 100644
index 0000000..4afb4cd
--- /dev/null
+++ b/src/CSF.Hosting/Core/Execution/Impl/HostedContextFactory.cs
@@ -0,0 +1,34 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Hosting
+{
+ public class HostedContextFactory : IContextFactory
+ {
+ public ILoggerFactory LoggerFactory { get; }
+
+ public HostedContextFactory(ILoggerFactory loggerFactory)
+ {
+ LoggerFactory = loggerFactory;
+ }
+
+ public virtual HostedCommandContext CreateContext()
+ {
+ var guid = Guid.NewGuid();
+ var logger = LoggerFactory.CreateLogger($"CSF.Command.Pipeline[{Guid.NewGuid()}]");
+
+ logger.LogTrace("Generating context with ID {}", guid);
+
+ return new(guid, logger);
+ }
+
+ public void Dispose()
+ {
+
+ }
+ }
+}
diff --git a/src/CSF.Hosting/Core/Execution/Impl/HostedModuleBase.cs b/src/CSF.Hosting/Core/Execution/Impl/HostedModuleBase.cs
new file mode 100644
index 0000000..96f8f23
--- /dev/null
+++ b/src/CSF.Hosting/Core/Execution/Impl/HostedModuleBase.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Hosting
+{
+ public class HostedModuleBase : ModuleBase
+ where T : HostedCommandContext
+ {
+ public ILogger Logger
+ {
+ get
+ => Context.Logger;
+ }
+ }
+}
diff --git a/src/CSF.Hosting/Core/HostedCommandManager.cs b/src/CSF.Hosting/Core/HostedCommandManager.cs
new file mode 100644
index 0000000..2a0cb51
--- /dev/null
+++ b/src/CSF.Hosting/Core/HostedCommandManager.cs
@@ -0,0 +1,31 @@
+using CSF.Reflection;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Hosting
+{
+ public class HostedCommandManager : CommandManager
+ {
+ public ILogger Logger { get; }
+
+ public IContextFactory ContextFactory { get; }
+
+ public HostedCommandManager(ILogger logger, IContextFactory factory, IServiceProvider services, CommandConfiguration configuration)
+ : base(services, configuration)
+ {
+ ContextFactory = factory;
+ Logger = logger;
+ }
+
+ public Task ExecuteAsync(params object[] args)
+ {
+ var context = ContextFactory.CreateContext();
+
+ return base.ExecuteAsync(context, args);
+ }
+ }
+}
diff --git a/src/CSF.Hosting/Core/HostedCommandManagerResolver.cs b/src/CSF.Hosting/Core/HostedCommandManagerResolver.cs
new file mode 100644
index 0000000..d3dc957
--- /dev/null
+++ b/src/CSF.Hosting/Core/HostedCommandManagerResolver.cs
@@ -0,0 +1,44 @@
+using CSF.Core;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Hosting
+{
+ public static class HostedCommandManagerResolver
+ {
+ public static IHostBuilder WithCommands(this IHostBuilder builder, [DisallowNull] Action configureDelegate)
+ {
+ return builder.WithCommands(configureDelegate);
+ }
+
+ public static IHostBuilder WithCommands(this IHostBuilder builder, [DisallowNull] Action configureDelegate)
+ where T : HostedCommandManager
+ {
+ return builder.WithCommands(configureDelegate);
+ }
+
+ public static IHostBuilder WithCommands(this IHostBuilder builder, [DisallowNull] Action configureDelegate)
+ where TManager : HostedCommandManager where TFactory : class, IContextFactory
+ {
+ builder.ConfigureServices((context, services) =>
+ {
+ var config = new CommandConfiguration();
+
+ configureDelegate(context, config);
+
+ services.WithCommands(config);
+
+ services.AddScoped();
+ });
+
+ return builder;
+ }
+ }
+}
diff --git a/src/CSF.Hosting/Helpers/HostBuilderHelper.cs b/src/CSF.Hosting/Helpers/HostBuilderHelper.cs
deleted file mode 100644
index 71ea6c6..0000000
--- a/src/CSF.Hosting/Helpers/HostBuilderHelper.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-
-namespace CSF.Hosting
-{
- ///
- /// Represents helper method for the .
- ///
- public static class HostBuilderHelper
- {
- ///
- /// Configures the with a .
- ///
- ///
- /// Configuration action for the host and manager.
- /// The same for chaining calls.
- public static IHostBuilder ConfigureCommandManager(this IHostBuilder hostBuilder, Action action = null)
- {
- hostBuilder.ConfigureCommandManager(action);
-
- return hostBuilder;
- }
-
- ///
- /// Configures the with a customized .
- ///
- /// The manager to bind to.
- ///
- /// Configuration action for the host and manager.
- /// The same for chaining calls.
- public static IHostBuilder ConfigureCommandManager(this IHostBuilder hostBuilder, Action action = null)
- where T : HostedCommandManager
- {
- hostBuilder.ConfigureServices((hostContext, services) =>
- {
- var fxContext = new CommandBuildingConfiguration();
-
- action?.Invoke(hostContext, fxContext);
-
- services.AddComponents(fxContext);
-
- services.AddHostedService();
- });
-
- return hostBuilder;
- }
- }
-}
diff --git a/src/CSF.Hosting/Helpers/ServiceHelpers.cs b/src/CSF.Hosting/Helpers/ServiceHelpers.cs
new file mode 100644
index 0000000..71ed31c
--- /dev/null
+++ b/src/CSF.Hosting/Helpers/ServiceHelpers.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Hosting.Helpers
+{
+ internal class ServiceHelpers
+ {
+ }
+}
diff --git a/src/CSF.Hosting/HostedCommandManager.cs b/src/CSF.Hosting/HostedCommandManager.cs
deleted file mode 100644
index cb565c1..0000000
--- a/src/CSF.Hosting/HostedCommandManager.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
-
-namespace CSF
-{
- ///
- /// Represents a host-managed command manager.
- ///
- public class HostedCommandManager : CommandManager, IHostedService
- {
- ///
- /// The logger used by the hosted manager.
- ///
- public ILogger Logger { get; }
-
- ///
- /// The parser used by the hosted manager.
- ///
- public TextParser Parser { get; }
-
- public HostedCommandManager(IServiceProvider serviceProvider)
- : this(serviceProvider.GetRequiredService>(), serviceProvider)
- {
-
- }
-
- public HostedCommandManager(ILogger logger, IServiceProvider serviceProvider) : base(serviceProvider)
- {
- Logger = logger;
- Parser = new TextParser();
- }
-
- public virtual Task StartAsync(CancellationToken cancellationToken)
- {
- Task.Run(async () => await RunAsync(cancellationToken), cancellationToken)
- .ContinueWith(async _ => await StopAsync(cancellationToken));
-
- return Task.CompletedTask;
- }
-
- ///
- /// Enters a loop through which commands are read and ran.
- ///
- ///
- ///
- ///
- public virtual async Task RunAsync(CancellationToken cancellationToken)
- {
- while (true)
- {
- var context = new CommandContext(Console.ReadLine(), Parser);
-
- var result = ExecuteAsync(context, new ExecutionOptions());
-
- if (result.Failed(out var failure))
- Logger.LogError(failure.Exception, "Command execution returned an exception.");
-
- await result;
- }
- }
-
- public virtual Task StopAsync(CancellationToken cancellationToken)
- => Task.CompletedTask;
- }
-}
From f658c68a5e458631935f32bc9dcacf8f26e26928 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Mon, 29 Jan 2024 21:04:09 +0100
Subject: [PATCH 10/40] Improvements to asynchronous execution approach
---
.../Core/Attributes/CommandAttribute.cs | 2 +-
.../Core/Attributes/ComplexAttribute.cs | 2 +-
.../Core/Attributes/DescriptionAttribute.cs | 2 +-
.../Core/Attributes/GroupAttribute.cs | 2 +-
.../Attributes/PrimaryConstructorAttribute.cs | 2 +-
.../Core/Attributes/PriorityAttribute.cs | 2 +-
.../Core/Attributes/RemainderAttribute.cs | 2 +-
src/CSF.Core/Core/CommandConfiguration.cs | 4 +-
src/CSF.Core/Core/CommandManager.cs | 129 +++++++++++++-----
src/CSF.Core/Core/CommandManagerResolver.cs | 1 -
.../Core/Configuration/TaskAwaitOptions.cs | 17 +++
.../Core/Execution/ICommandContext.cs | 8 +-
src/CSF.Core/Core/Execution/IResultHandler.cs | 13 ++
.../Core/Execution/Impl/CommandContext.cs | 32 ++++-
src/CSF.Core/Core/Execution/ModuleBase.cs | 6 +-
.../Results/{IResult.cs => ICommandResult.cs} | 6 +-
src/CSF.Core/Core/Results/Impl/CheckResult.cs | 2 +-
src/CSF.Core/Core/Results/Impl/MatchResult.cs | 2 +-
src/CSF.Core/Core/Results/Impl/ReadResult.cs | 2 +-
src/CSF.Core/Core/Results/Impl/RunResult.cs | 2 +-
.../Core/Results/Impl/SearchResult.cs | 2 +-
src/CSF.Core/Helpers/ExecutionHelpers.cs | 15 +-
src/CSF.Core/Helpers/ReflectionHelpers.cs | 5 +-
src/CSF.Core/Helpers/ServiceHelpers.cs | 5 +-
src/CSF.Core/Helpers/ThrowHelpers.cs | 2 +-
.../Preconditions/PreconditionAttribute.cs | 5 +-
src/CSF.Core/Reflection/Impl/ArgumentInfo.cs | 3 +-
src/CSF.Core/Reflection/Impl/CommandInfo.cs | 21 +--
.../TypeReaders/Impl/BaseTypeReader.cs | 5 +-
.../TypeReaders/Impl/ColorTypeReader.cs | 5 +-
.../TypeReaders/Impl/EnumTypeReader.cs | 5 +-
.../TypeReaders/Impl/TimeSpanTypeReader.cs | 5 +-
src/CSF.Core/TypeReaders/TypeReader.cs | 14 +-
src/CSF.Hosting/CSF.Hosting.csproj | 3 +
.../Core/Execution/IContextFactory.cs | 24 ----
.../Execution/Impl/HostedCommandContext.cs | 55 --------
.../Execution/Impl/HostedContextFactory.cs | 34 -----
src/CSF.Hosting/Core/HostedCommandManager.cs | 31 -----
src/CSF.Hosting/Helpers/ServiceHelpers.cs | 8 +-
.../Hosting/Execution/IActionFactory.cs | 22 +++
.../Execution/Impl/HostedCommandContext.cs | 42 ++++++
.../Execution/Impl/HostedModuleBase.cs | 8 +-
.../Hosting/HostedCommandManager.cs | 72 ++++++++++
.../HostedCommandManagerResolver.cs | 28 ++--
src/CSF.Tests.Console/Complex/ComplexType.cs | 4 +-
.../Complex/ComplexerType.cs | 4 +-
src/CSF.Tests.Console/Modules/AsyncModule.cs | 8 +-
src/CSF.Tests.Console/Modules/Module.cs | 35 ++---
src/CSF.Tests.Console/Program.cs | 40 ++----
src/CSF.Tests.Hosting/Factory.cs | 53 +++++++
src/CSF.Tests.Hosting/Module.cs | 9 +-
src/CSF.Tests.Hosting/Program.cs | 12 +-
52 files changed, 486 insertions(+), 336 deletions(-)
create mode 100644 src/CSF.Core/Core/Configuration/TaskAwaitOptions.cs
create mode 100644 src/CSF.Core/Core/Execution/IResultHandler.cs
rename src/CSF.Core/Core/Results/{IResult.cs => ICommandResult.cs} (51%)
delete mode 100644 src/CSF.Hosting/Core/Execution/IContextFactory.cs
delete mode 100644 src/CSF.Hosting/Core/Execution/Impl/HostedCommandContext.cs
delete mode 100644 src/CSF.Hosting/Core/Execution/Impl/HostedContextFactory.cs
delete mode 100644 src/CSF.Hosting/Core/HostedCommandManager.cs
create mode 100644 src/CSF.Hosting/Hosting/Execution/IActionFactory.cs
create mode 100644 src/CSF.Hosting/Hosting/Execution/Impl/HostedCommandContext.cs
rename src/CSF.Hosting/{Core => Hosting}/Execution/Impl/HostedModuleBase.cs (59%)
create mode 100644 src/CSF.Hosting/Hosting/HostedCommandManager.cs
rename src/CSF.Hosting/{Core => Hosting}/HostedCommandManagerResolver.cs (50%)
create mode 100644 src/CSF.Tests.Hosting/Factory.cs
diff --git a/src/CSF.Core/Core/Attributes/CommandAttribute.cs b/src/CSF.Core/Core/Attributes/CommandAttribute.cs
index 3be9948..d89ef0f 100644
--- a/src/CSF.Core/Core/Attributes/CommandAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/CommandAttribute.cs
@@ -1,7 +1,7 @@
using CSF.Helpers;
using System.Diagnostics.CodeAnalysis;
-namespace CSF
+namespace CSF.Core
{
///
/// An attribute that represents the required info to map a command.
diff --git a/src/CSF.Core/Core/Attributes/ComplexAttribute.cs b/src/CSF.Core/Core/Attributes/ComplexAttribute.cs
index 346132d..9cc14e0 100644
--- a/src/CSF.Core/Core/Attributes/ComplexAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/ComplexAttribute.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Core
{
///
/// Marks a parameter as complex, which will attempt to fetch the primary constructor values and use those as command parameters.
diff --git a/src/CSF.Core/Core/Attributes/DescriptionAttribute.cs b/src/CSF.Core/Core/Attributes/DescriptionAttribute.cs
index 89c3064..d622fef 100644
--- a/src/CSF.Core/Core/Attributes/DescriptionAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/DescriptionAttribute.cs
@@ -1,7 +1,7 @@
using CSF.Helpers;
using System.Diagnostics.CodeAnalysis;
-namespace CSF
+namespace CSF.Core
{
///
/// Represents the description of a command.
diff --git a/src/CSF.Core/Core/Attributes/GroupAttribute.cs b/src/CSF.Core/Core/Attributes/GroupAttribute.cs
index 39506e2..09b3fb0 100644
--- a/src/CSF.Core/Core/Attributes/GroupAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/GroupAttribute.cs
@@ -1,7 +1,7 @@
using CSF.Helpers;
using System.Diagnostics.CodeAnalysis;
-namespace CSF
+namespace CSF.Core
{
///
/// Represents a command group, functioning much like subcommands.
diff --git a/src/CSF.Core/Core/Attributes/PrimaryConstructorAttribute.cs b/src/CSF.Core/Core/Attributes/PrimaryConstructorAttribute.cs
index e38a26d..3cdd919 100644
--- a/src/CSF.Core/Core/Attributes/PrimaryConstructorAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/PrimaryConstructorAttribute.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Core
{
///
/// Represents an attribute that sets a specified constructor as the dependency injection constructor.
diff --git a/src/CSF.Core/Core/Attributes/PriorityAttribute.cs b/src/CSF.Core/Core/Attributes/PriorityAttribute.cs
index b33d60b..a6a55c5 100644
--- a/src/CSF.Core/Core/Attributes/PriorityAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/PriorityAttribute.cs
@@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;
-namespace CSF
+namespace CSF.Core
{
///
/// Represents an attribute that can prioritize one result over another when multiple matches were found.
diff --git a/src/CSF.Core/Core/Attributes/RemainderAttribute.cs b/src/CSF.Core/Core/Attributes/RemainderAttribute.cs
index 4e644dc..b1e95ff 100644
--- a/src/CSF.Core/Core/Attributes/RemainderAttribute.cs
+++ b/src/CSF.Core/Core/Attributes/RemainderAttribute.cs
@@ -1,4 +1,4 @@
-namespace CSF
+namespace CSF.Core
{
///
/// Defines that this parameter should be the remainder of the command phrase.
diff --git a/src/CSF.Core/Core/CommandConfiguration.cs b/src/CSF.Core/Core/CommandConfiguration.cs
index 20af470..333bcd2 100644
--- a/src/CSF.Core/Core/CommandConfiguration.cs
+++ b/src/CSF.Core/Core/CommandConfiguration.cs
@@ -1,12 +1,14 @@
using CSF.TypeReaders;
using System.Reflection;
-namespace CSF
+namespace CSF.Core
{
public sealed class CommandConfiguration
{
public Assembly[] Assemblies { get; set; } = [ Assembly.GetEntryAssembly() ];
public TypeReader[] TypeReaders { get; set; } = [];
+
+ public TaskAwaitOptions ExecutionPattern { get; set; }
}
}
diff --git a/src/CSF.Core/Core/CommandManager.cs b/src/CSF.Core/Core/CommandManager.cs
index 70f5908..0cd4141 100644
--- a/src/CSF.Core/Core/CommandManager.cs
+++ b/src/CSF.Core/Core/CommandManager.cs
@@ -1,18 +1,24 @@
-using CSF.TypeReaders;
+using CSF.Exceptions;
using CSF.Helpers;
using CSF.Reflection;
-using CSF.Exceptions;
+using CSF.TypeReaders;
+using Microsoft.Extensions.DependencyInjection;
+using System.ComponentModel;
[assembly: CLSCompliant(true)]
-namespace CSF
+namespace CSF.Core
{
- public class CommandManager
+ public class CommandManager : IDisposable
{
- public IServiceProvider Services { get; }
+ private readonly object _searchLock = new();
public IReadOnlySet Components { get; }
+ public IServiceProvider Services { get; }
+
+ public IResultHandler ResultHandler { get; }
+
public TypeReader[] TypeReaders { get; }
public CommandConfiguration Configuration { get; }
@@ -32,53 +38,96 @@ public CommandManager(IServiceProvider services, CommandConfiguration configurat
Services = services;
+ ResultHandler = services.GetService();
+
Configuration = configuration;
}
- public virtual async Task ExecuteAsync(ICommandContext context, params object[] args)
+ public void Execute(ICommandContext context, params object[] args)
+ => ExecuteAsync(context, args).Wait();
+
+ public Task ExecuteAsync(ICommandContext context, params object[] args)
+ => ExecuteAsync(context, args, cancellationToken: default);
+
+ public async Task ExecuteAsync(ICommandContext context, object[] args, CancellationToken cancellationToken = default)
+ {
+ switch (Configuration.ExecutionPattern)
+ {
+ case TaskAwaitOptions.Await:
+ {
+ context.LogDebug("Starting execution. Execution pattern = [Await].");
+ await ExecuteInternalAsync(context, args, cancellationToken);
+ }
+ return;
+ case TaskAwaitOptions.Discard:
+ {
+ context.LogDebug("Starting execution. Execution pattern = [Discard].");
+ _ = ExecuteInternalAsync(context, args, cancellationToken);
+ }
+ return;
+ }
+ }
+
+ public IEnumerable Search(object[] args)
+ {
+ // recursively search for commands in the execution.
+ lock (_searchLock)
+ {
+ return Components.RecursiveSearch(args, 0);
+ }
+ }
+
+ #region Executing
+ private async Task ExecuteInternalAsync(ICommandContext context, object[] args, CancellationToken cancellationToken)
{
- // search all relevant commands.
var searches = Search(args);
- // define a fallback for unsuccesful execution.
- MatchResult? fallback = default;
+ var c = 0;
- // order searches by descending for priority definitions.
foreach (var search in searches.OrderByDescending(x => x.Command.Priority))
{
- var match = await MatchAsync(context, search, args);
+ c++;
- if (fallback is not null)
- fallback = match;
+ var match = await MatchAsync(context, search, args, cancellationToken);
+ // enter the invocation logic when a match is succesful.
if (match.Success)
- return await RunAsync(context, match);
- }
+ {
+ var result = await RunAsync(context, match, cancellationToken);
+ await ResultHandler.HandleAsync(context, result, cancellationToken);
- if (!fallback.HasValue)
- return new SearchResult(new SearchException("No command was found with the provided input."));
+ return;
+ }
- return fallback;
- }
+ context.TrySetFallback(match);
+ }
- public virtual IEnumerable Search(object[] args)
- {
- // recursively search for commands in the execution.
- return Components.RecursiveSearch(args, 0);
+ // if no searches were found, we send searchfailure.
+ if (c is 0)
+ {
+ await ResultHandler.HandleAsync(context, new SearchResult(new SearchException("No commands were found with the provided input.")), cancellationToken);
+ }
+
+ // if there is a fallback present, we send matchfailure.
+ if (context.TryGetFallback(out var fallback))
+ {
+ await ResultHandler.HandleAsync(context, fallback, cancellationToken);
+ }
}
+ #endregion
#region Matching
- private async ValueTask MatchAsync(ICommandContext context, SearchResult search, object[] args)
+ private async ValueTask MatchAsync(ICommandContext context, SearchResult search, object[] args, CancellationToken cancellationToken)
{
// check command preconditions.
- var check = await CheckAsync(context, search.Command);
+ var check = await CheckAsync(context, search.Command, cancellationToken);
// verify check success, if not, return the failure.
if (!check.Success)
return new(search.Command, new MatchException("Command failed to reach execution state. View inner exception for more details.", check.Exception));
// read the command parameters in right order.
- var readResult = await ReadAsync(context, search, args);
+ var readResult = await ReadAsync(context, search, args, cancellationToken);
// exchange the reads for result, verifying successes in the process.
var reads = new object[readResult.Length];
@@ -97,7 +146,7 @@ private async ValueTask MatchAsync(ICommandContext context, SearchR
#endregion
#region Reading
- private async ValueTask ReadAsync(ICommandContext context, SearchResult search, object[] args)
+ private async ValueTask ReadAsync(ICommandContext context, SearchResult search, object[] args, CancellationToken cancellationToken)
{
context.LogDebug("Attempting argument conversion for {}", search.Command);
@@ -110,15 +159,15 @@ private async ValueTask ReadAsync(ICommandContext context, SearchR
// check if input equals command length.
if (search.Command.MaxLength == length)
- return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0);
+ return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0, cancellationToken);
// check if input is longer than command, but remainder to concatenate.
if (search.Command.MaxLength <= length && search.Command.HasRemainder)
- return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0);
+ return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0, cancellationToken);
// check if input is shorter than command, but optional parameters to replace.
if (search.Command.MaxLength > length && search.Command.MinLength <= length)
- return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0);
+ return await search.Command.Parameters.RecursiveReadAsync(context, args[length..], 0, cancellationToken);
// input is too long or too short.
return [];
@@ -126,13 +175,13 @@ private async ValueTask ReadAsync(ICommandContext context, SearchR
#endregion
#region Checking
- private async ValueTask CheckAsync(ICommandContext context, CommandInfo command)
+ private async ValueTask CheckAsync(ICommandContext context, CommandInfo command, CancellationToken cancellationToken)
{
context.LogDebug("Attempting validations for {}", command);
foreach (var precon in command.Preconditions)
{
- var result = await precon.EvaluateAsync(context, command);
+ var result = await precon.EvaluateAsync(context, command, cancellationToken);
if (!result.Success)
return result;
@@ -142,7 +191,7 @@ private async ValueTask CheckAsync(ICommandContext context, Command
#endregion
#region Running
- private async ValueTask RunAsync(ICommandContext context, MatchResult match)
+ private async ValueTask RunAsync(ICommandContext context, MatchResult match, CancellationToken cancellationToken)
{
try
{
@@ -154,11 +203,11 @@ private async ValueTask RunAsync(ICommandContext context, MatchResult
module.Command = match.Command;
module.Services = Services;
- await module.BeforeExecuteAsync();
+ await module.BeforeExecuteAsync(cancellationToken);
var value = match.Command.Target.Invoke(module, match.Reads);
- await module.AfterExecuteAsync();
+ await module.AfterExecuteAsync(cancellationToken);
return module.ReturnTypeResolve(value);
}
@@ -189,5 +238,15 @@ private IEnumerable BuildComponents(CommandConfiguration configurati
}
}
#endregion
+
+ public void Dispose()
+ {
+
+ }
+
+ public override string ToString()
+ {
+
+ }
}
}
diff --git a/src/CSF.Core/Core/CommandManagerResolver.cs b/src/CSF.Core/Core/CommandManagerResolver.cs
index a17f189..db13886 100644
--- a/src/CSF.Core/Core/CommandManagerResolver.cs
+++ b/src/CSF.Core/Core/CommandManagerResolver.cs
@@ -1,7 +1,6 @@
using CSF.Helpers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
-using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace CSF.Core
diff --git a/src/CSF.Core/Core/Configuration/TaskAwaitOptions.cs b/src/CSF.Core/Core/Configuration/TaskAwaitOptions.cs
new file mode 100644
index 0000000..3f98ee6
--- /dev/null
+++ b/src/CSF.Core/Core/Configuration/TaskAwaitOptions.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Core
+{
+ public enum TaskAwaitOptions
+ {
+ Default = Await,
+
+ Await = 0,
+
+ Discard = 1,
+ }
+}
diff --git a/src/CSF.Core/Core/Execution/ICommandContext.cs b/src/CSF.Core/Core/Execution/ICommandContext.cs
index 0bed802..bc20df2 100644
--- a/src/CSF.Core/Core/Execution/ICommandContext.cs
+++ b/src/CSF.Core/Core/Execution/ICommandContext.cs
@@ -1,6 +1,6 @@
-using Microsoft.Extensions.DependencyInjection;
+using System.Diagnostics.CodeAnalysis;
-namespace CSF
+namespace CSF.Core
{
public interface ICommandContext
{
@@ -15,5 +15,9 @@ public interface ICommandContext
public void LogError(string message, params object[] args);
public void LogCritical(string message, params object[] args);
+
+ internal bool TryGetFallback([NotNullWhen(true)] out ICommandResult result);
+
+ internal void TrySetFallback(ICommandResult result);
}
}
diff --git a/src/CSF.Core/Core/Execution/IResultHandler.cs b/src/CSF.Core/Core/Execution/IResultHandler.cs
new file mode 100644
index 0000000..0a5c932
--- /dev/null
+++ b/src/CSF.Core/Core/Execution/IResultHandler.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Core
+{
+ public interface IResultHandler : IDisposable
+ {
+ public Task HandleAsync(ICommandContext context, ICommandResult result, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/CSF.Core/Core/Execution/Impl/CommandContext.cs b/src/CSF.Core/Core/Execution/Impl/CommandContext.cs
index 6ba3685..d7fbec2 100644
--- a/src/CSF.Core/Core/Execution/Impl/CommandContext.cs
+++ b/src/CSF.Core/Core/Execution/Impl/CommandContext.cs
@@ -1,9 +1,10 @@
-using CSF.Reflection;
-
-namespace CSF
+namespace CSF.Core
{
- public abstract class CommandContext : ICommandContext
+ public class CommandContext : ICommandContext
{
+ private readonly object _lock = new();
+ private ICommandResult _fallback;
+
public virtual void LogCritical(string message, params object[] args)
{
@@ -31,7 +32,28 @@ public virtual void LogTrace(string message, params object[] args)
public virtual void LogWarning(string message, params object[] args)
{
-
+
+ }
+
+ bool ICommandContext.TryGetFallback(out ICommandResult result)
+ {
+ lock (_lock)
+ {
+ result = _fallback;
+
+ return _fallback != null;
+ }
+ }
+
+ void ICommandContext.TrySetFallback(ICommandResult result)
+ {
+ lock (_lock)
+ {
+ if (_fallback != null)
+ {
+ _fallback = result;
+ }
+ }
}
}
}
diff --git a/src/CSF.Core/Core/Execution/ModuleBase.cs b/src/CSF.Core/Core/Execution/ModuleBase.cs
index ce96cd8..2b34f93 100644
--- a/src/CSF.Core/Core/Execution/ModuleBase.cs
+++ b/src/CSF.Core/Core/Execution/ModuleBase.cs
@@ -1,6 +1,6 @@
using CSF.Reflection;
-namespace CSF
+namespace CSF.Core
{
public abstract class ModuleBase : ModuleBase
where T : ICommandContext
@@ -50,12 +50,12 @@ public virtual RunResult HandleUnknownReturnType(object value)
return new RunResult(Command, exception: null);
}
- public virtual ValueTask BeforeExecuteAsync()
+ public virtual ValueTask BeforeExecuteAsync(CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
- public virtual ValueTask AfterExecuteAsync()
+ public virtual ValueTask AfterExecuteAsync(CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
diff --git a/src/CSF.Core/Core/Results/IResult.cs b/src/CSF.Core/Core/Results/ICommandResult.cs
similarity index 51%
rename from src/CSF.Core/Core/Results/IResult.cs
rename to src/CSF.Core/Core/Results/ICommandResult.cs
index 5cda7ce..a241e95 100644
--- a/src/CSF.Core/Core/Results/IResult.cs
+++ b/src/CSF.Core/Core/Results/ICommandResult.cs
@@ -1,6 +1,8 @@
-namespace CSF
+using System.Diagnostics.CodeAnalysis;
+
+namespace CSF
{
- public interface IResult
+ public interface ICommandResult
{
public Exception Exception { get; }
diff --git a/src/CSF.Core/Core/Results/Impl/CheckResult.cs b/src/CSF.Core/Core/Results/Impl/CheckResult.cs
index e1fe927..09e5ac0 100644
--- a/src/CSF.Core/Core/Results/Impl/CheckResult.cs
+++ b/src/CSF.Core/Core/Results/Impl/CheckResult.cs
@@ -1,6 +1,6 @@
namespace CSF
{
- public readonly struct CheckResult : IResult
+ public readonly struct CheckResult : ICommandResult
{
public Exception Exception { get; } = null;
diff --git a/src/CSF.Core/Core/Results/Impl/MatchResult.cs b/src/CSF.Core/Core/Results/Impl/MatchResult.cs
index 89b26ff..725f9dc 100644
--- a/src/CSF.Core/Core/Results/Impl/MatchResult.cs
+++ b/src/CSF.Core/Core/Results/Impl/MatchResult.cs
@@ -2,7 +2,7 @@
namespace CSF
{
- public readonly struct MatchResult : IResult
+ public readonly struct MatchResult : ICommandResult
{
public Exception Exception { get; } = null;
diff --git a/src/CSF.Core/Core/Results/Impl/ReadResult.cs b/src/CSF.Core/Core/Results/Impl/ReadResult.cs
index 263841d..159bfc9 100644
--- a/src/CSF.Core/Core/Results/Impl/ReadResult.cs
+++ b/src/CSF.Core/Core/Results/Impl/ReadResult.cs
@@ -1,6 +1,6 @@
namespace CSF
{
- public readonly struct ReadResult : IResult
+ public readonly struct ReadResult : ICommandResult
{
public Exception Exception { get; } = null;
diff --git a/src/CSF.Core/Core/Results/Impl/RunResult.cs b/src/CSF.Core/Core/Results/Impl/RunResult.cs
index cac745c..36f8edf 100644
--- a/src/CSF.Core/Core/Results/Impl/RunResult.cs
+++ b/src/CSF.Core/Core/Results/Impl/RunResult.cs
@@ -2,7 +2,7 @@
namespace CSF
{
- public readonly struct RunResult : IResult
+ public readonly struct RunResult : ICommandResult
{
public Exception Exception { get; } = null;
diff --git a/src/CSF.Core/Core/Results/Impl/SearchResult.cs b/src/CSF.Core/Core/Results/Impl/SearchResult.cs
index a0605e0..710d3a4 100644
--- a/src/CSF.Core/Core/Results/Impl/SearchResult.cs
+++ b/src/CSF.Core/Core/Results/Impl/SearchResult.cs
@@ -2,7 +2,7 @@
namespace CSF
{
- public readonly struct SearchResult : IResult
+ public readonly struct SearchResult : ICommandResult
{
public Exception Exception { get; } = null;
diff --git a/src/CSF.Core/Helpers/ExecutionHelpers.cs b/src/CSF.Core/Helpers/ExecutionHelpers.cs
index 8048168..c36a617 100644
--- a/src/CSF.Core/Helpers/ExecutionHelpers.cs
+++ b/src/CSF.Core/Helpers/ExecutionHelpers.cs
@@ -1,4 +1,5 @@
-using CSF.Reflection;
+using CSF.Core;
+using CSF.Reflection;
namespace CSF.Helpers
{
@@ -29,9 +30,9 @@ public static IEnumerable RecursiveSearch(this IEnumerable RecursiveReadAsync(this IArgument[] param, ICommandContext context, object[] args, int index)
+ public static async Task RecursiveReadAsync(this IArgument[] param, ICommandContext context, object[] args, int index, CancellationToken cancellationToken)
{
- static async ValueTask ReadAsync(IArgument param, ICommandContext context, object arg)
+ static async ValueTask ReadAsync(IArgument param, ICommandContext context, object arg, CancellationToken cancellationToken)
{
if (arg.GetType() == param.Type)
return new(arg);
@@ -39,7 +40,7 @@ static async ValueTask ReadAsync(IArgument param, ICommandContext co
if (param.IsNullable && arg is null or "null" or "nothing")
return new(arg);
- return await param.TypeReader.ObjectEvaluateAsync(context, param, arg);
+ return await param.TypeReader.ObjectEvaluateAsync(context, param, arg, cancellationToken);
}
var results = new ReadResult[param.Length];
@@ -54,7 +55,7 @@ static async ValueTask ReadAsync(IArgument param, ICommandContext co
if (parameter.Type == typeof(string))
results[i] = new(input);
else
- results[i] = await ReadAsync(parameter, context, input);
+ results[i] = await ReadAsync(parameter, context, input, cancellationToken);
break;
}
@@ -67,7 +68,7 @@ static async ValueTask ReadAsync(IArgument param, ICommandContext co
if (parameter is ComplexArgumentInfo complex)
{
- var result = await complex.Parameters.RecursiveReadAsync(context, args, index);
+ var result = await complex.Parameters.RecursiveReadAsync(context, args, index, cancellationToken);
index += result.Length;
@@ -86,7 +87,7 @@ static async ValueTask ReadAsync(IArgument param, ICommandContext co
continue;
}
- results[i] = await ReadAsync(parameter, context, args[index]);
+ results[i] = await ReadAsync(parameter, context, args[index], cancellationToken);
index++;
}
diff --git a/src/CSF.Core/Helpers/ReflectionHelpers.cs b/src/CSF.Core/Helpers/ReflectionHelpers.cs
index b753049..5ea638f 100644
--- a/src/CSF.Core/Helpers/ReflectionHelpers.cs
+++ b/src/CSF.Core/Helpers/ReflectionHelpers.cs
@@ -1,7 +1,8 @@
-using CSF.TypeReaders;
+using CSF.Core;
+using CSF.Preconditions;
using CSF.Reflection;
+using CSF.TypeReaders;
using System.Reflection;
-using CSF.Preconditions;
namespace CSF.Helpers
{
diff --git a/src/CSF.Core/Helpers/ServiceHelpers.cs b/src/CSF.Core/Helpers/ServiceHelpers.cs
index c1c0cdb..670617f 100644
--- a/src/CSF.Core/Helpers/ServiceHelpers.cs
+++ b/src/CSF.Core/Helpers/ServiceHelpers.cs
@@ -1,9 +1,10 @@
-using Microsoft.Extensions.DependencyInjection;
+using CSF.Core;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace CSF.Helpers
{
- internal static class ServiceHelpers
+ public static class ServiceHelpers
{
public static IServiceCollection ModulesAddTransient(this IServiceCollection collection, CommandConfiguration configuration)
{
diff --git a/src/CSF.Core/Helpers/ThrowHelpers.cs b/src/CSF.Core/Helpers/ThrowHelpers.cs
index 2617c61..1a651ac 100644
--- a/src/CSF.Core/Helpers/ThrowHelpers.cs
+++ b/src/CSF.Core/Helpers/ThrowHelpers.cs
@@ -4,7 +4,7 @@
namespace CSF.Helpers
{
- internal static class ThrowHelpers
+ public static class ThrowHelpers
{
[DoesNotReturn]
public static void InvalidOp([DisallowNull] string failureMessage)
diff --git a/src/CSF.Core/Preconditions/PreconditionAttribute.cs b/src/CSF.Core/Preconditions/PreconditionAttribute.cs
index 0ea3453..0b14fe6 100644
--- a/src/CSF.Core/Preconditions/PreconditionAttribute.cs
+++ b/src/CSF.Core/Preconditions/PreconditionAttribute.cs
@@ -1,6 +1,7 @@
-using CSF.Reflection;
+using CSF.Core;
using CSF.Exceptions;
using CSF.Helpers;
+using CSF.Reflection;
using System.Diagnostics.CodeAnalysis;
namespace CSF.Preconditions
@@ -10,7 +11,7 @@ public abstract class PreconditionAttribute : Attribute
{
private static readonly string _exHeader = "Precondition result halted further command execution. View inner exception for more details.";
- public abstract ValueTask EvaluateAsync(ICommandContext context, CommandInfo command);
+ public abstract ValueTask EvaluateAsync(ICommandContext context, CommandInfo command, CancellationToken cancellationToken);
public static CheckResult Error([DisallowNull] Exception exception)
{
diff --git a/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs b/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
index cc41ec9..a802ca4 100644
--- a/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
+++ b/src/CSF.Core/Reflection/Impl/ArgumentInfo.cs
@@ -1,4 +1,5 @@
-using CSF.Helpers;
+using CSF.Core;
+using CSF.Helpers;
using CSF.TypeReaders;
using System.Reflection;
diff --git a/src/CSF.Core/Reflection/Impl/CommandInfo.cs b/src/CSF.Core/Reflection/Impl/CommandInfo.cs
index a27207a..ee92d3c 100644
--- a/src/CSF.Core/Reflection/Impl/CommandInfo.cs
+++ b/src/CSF.Core/Reflection/Impl/CommandInfo.cs
@@ -1,4 +1,5 @@
-using CSF.Helpers;
+using CSF.Core;
+using CSF.Helpers;
using CSF.Preconditions;
using CSF.TypeReaders;
using System.Reflection;
@@ -8,43 +9,30 @@ namespace CSF.Reflection
public sealed class CommandInfo : IConditional, IArgumentBucket
{
-
public string Name { get; }
-
public Attribute[] Attributes { get; }
-
public PreconditionAttribute[] Preconditions { get; }
-
public bool HasPreconditions { get; }
-
public IArgument[] Parameters { get; }
-
public bool HasParameters { get; }
-
public bool HasRemainder { get; }
-
public int MinLength { get; }
-
public int MaxLength { get; }
-
public string[] Aliases { get; }
-
public byte Priority { get; }
-
public ModuleInfo Module { get; }
-
public MethodInfo Target { get; }
internal CommandInfo(ModuleInfo module, MethodInfo method, string[] aliases, IDictionary typeReaders)
@@ -63,6 +51,11 @@ internal CommandInfo(ModuleInfo module, MethodInfo method, string[] aliases, IDi
}
}
+ if (method.ReturnType == typeof(Task))
+ {
+
+ }
+
Priority = attributes.SelectFirstOrDefault()?.Priority ?? 0;
Target = method;
diff --git a/src/CSF.Core/TypeReaders/Impl/BaseTypeReader.cs b/src/CSF.Core/TypeReaders/Impl/BaseTypeReader.cs
index a1570b4..41a3c16 100644
--- a/src/CSF.Core/TypeReaders/Impl/BaseTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/Impl/BaseTypeReader.cs
@@ -1,4 +1,5 @@
-using CSF.Reflection;
+using CSF.Core;
+using CSF.Reflection;
namespace CSF.TypeReaders
{
@@ -8,7 +9,7 @@ internal class BaseTypeReader : TypeReader
private readonly static Lazy> _container = new(ValueGenerator);
- public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value, CancellationToken cancellationToken)
{
var parser = _container.Value[Type] as Tpd;
diff --git a/src/CSF.Core/TypeReaders/Impl/ColorTypeReader.cs b/src/CSF.Core/TypeReaders/Impl/ColorTypeReader.cs
index f53cbd2..3f5fe66 100644
--- a/src/CSF.Core/TypeReaders/Impl/ColorTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/Impl/ColorTypeReader.cs
@@ -1,4 +1,5 @@
-using CSF.Reflection;
+using CSF.Core;
+using CSF.Reflection;
using System.Drawing;
using System.Globalization;
using System.Reflection;
@@ -46,7 +47,7 @@ public ColorTypeReader()
_spacedColors = spacedNames;
}
- public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value, CancellationToken cancellationToken)
{
if (int.TryParse(value.Replace("#", "").Replace("0x", ""), NumberStyles.HexNumber, null, out var hexNumber))
return ValueTask.FromResult(Success(Color.FromArgb(hexNumber)));
diff --git a/src/CSF.Core/TypeReaders/Impl/EnumTypeReader.cs b/src/CSF.Core/TypeReaders/Impl/EnumTypeReader.cs
index 3f1d258..73688b1 100644
--- a/src/CSF.Core/TypeReaders/Impl/EnumTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/Impl/EnumTypeReader.cs
@@ -1,4 +1,5 @@
-using CSF.Reflection;
+using CSF.Core;
+using CSF.Reflection;
namespace CSF.TypeReaders
{
@@ -8,7 +9,7 @@ internal class EnumTypeReader(Type targetEnumType) : TypeReader
public override Type Type { get; } = targetEnumType;
- public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value, CancellationToken cancellationToken)
{
if (Enum.TryParse(Type, value, true, out var result))
return ValueTask.FromResult(Success(result));
diff --git a/src/CSF.Core/TypeReaders/Impl/TimeSpanTypeReader.cs b/src/CSF.Core/TypeReaders/Impl/TimeSpanTypeReader.cs
index 8587351..0435a31 100644
--- a/src/CSF.Core/TypeReaders/Impl/TimeSpanTypeReader.cs
+++ b/src/CSF.Core/TypeReaders/Impl/TimeSpanTypeReader.cs
@@ -1,4 +1,5 @@
-using CSF.Reflection;
+using CSF.Core;
+using CSF.Reflection;
using System.Text.RegularExpressions;
namespace CSF.TypeReaders
@@ -34,7 +35,7 @@ public TimeSpanTypeReader()
};
}
- public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value)
+ public override ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value, CancellationToken cancellationToken)
{
if (!TimeSpan.TryParse(value, out TimeSpan span))
{
diff --git a/src/CSF.Core/TypeReaders/TypeReader.cs b/src/CSF.Core/TypeReaders/TypeReader.cs
index 951d76f..5210d3d 100644
--- a/src/CSF.Core/TypeReaders/TypeReader.cs
+++ b/src/CSF.Core/TypeReaders/TypeReader.cs
@@ -1,8 +1,8 @@
-using CSF.Reflection;
+using CSF.Core;
using CSF.Exceptions;
using CSF.Helpers;
+using CSF.Reflection;
using System.Diagnostics.CodeAnalysis;
-using System.Diagnostics;
namespace CSF.TypeReaders
{
@@ -10,7 +10,7 @@ public abstract class TypeReader : TypeReader
{
public override Type Type { get; } = typeof(T);
- public override abstract ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value);
+ public override abstract ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value, CancellationToken cancellationToken);
}
public abstract class TypeReader
@@ -19,17 +19,17 @@ public abstract class TypeReader
public abstract Type Type { get; }
- public abstract ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value);
+ public abstract ValueTask EvaluateAsync(ICommandContext context, IArgument parameter, string value, CancellationToken cancellationToken);
- internal ValueTask ObjectEvaluateAsync(ICommandContext context, IArgument parameter, object value)
+ internal ValueTask ObjectEvaluateAsync(ICommandContext context, IArgument parameter, object value, CancellationToken cancellationToken)
{
if (value.GetType() == Type)
return ValueTask.FromResult(new ReadResult(value));
if (value is string str)
- return EvaluateAsync(context, parameter, str);
+ return EvaluateAsync(context, parameter, str, cancellationToken);
- return EvaluateAsync(context, parameter, value.ToString());
+ return EvaluateAsync(context, parameter, value.ToString(), cancellationToken);
}
public virtual ReadResult Error([DisallowNull] Exception exception)
diff --git a/src/CSF.Hosting/CSF.Hosting.csproj b/src/CSF.Hosting/CSF.Hosting.csproj
index 7cb0313..99d036a 100644
--- a/src/CSF.Hosting/CSF.Hosting.csproj
+++ b/src/CSF.Hosting/CSF.Hosting.csproj
@@ -3,12 +3,15 @@
net8.0
enable
+ CSF
2.1
2.1
2.1.1.0
2.1
+ true
+
Armano den Boef and CSF contributors.
https://github.com/Rozen4334/CSF.NET
diff --git a/src/CSF.Hosting/Core/Execution/IContextFactory.cs b/src/CSF.Hosting/Core/Execution/IContextFactory.cs
deleted file mode 100644
index 5d0a360..0000000
--- a/src/CSF.Hosting/Core/Execution/IContextFactory.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF.Hosting
-{
- public interface IContextFactory : IDisposable
- {
- public ICommandContext CreateContext();
- }
-
- public interface IContextFactory : IContextFactory
- where T : ICommandContext
- {
- public new T CreateContext();
-
- ICommandContext IContextFactory.CreateContext()
- {
- return CreateContext();
- }
- }
-}
diff --git a/src/CSF.Hosting/Core/Execution/Impl/HostedCommandContext.cs b/src/CSF.Hosting/Core/Execution/Impl/HostedCommandContext.cs
deleted file mode 100644
index 5f6d458..0000000
--- a/src/CSF.Hosting/Core/Execution/Impl/HostedCommandContext.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF.Hosting
-{
- public class HostedCommandContext : ICommandContext
- {
- internal Guid Id { get; }
-
- public ILogger Logger { get; }
-
- public HostedCommandContext(Guid id, ILogger logger)
- {
- Id = id;
- Logger = logger;
- }
-
- public void LogTrace(string message, params object[] args)
- {
- Logger.Log(LogLevel.Trace, message, args);
- }
-
- public void LogDebug(string message, params object[] args)
- {
- Logger.Log(LogLevel.Debug, message, args);
- }
-
- public void LogInformation(string message, params object[] args)
- {
- Logger.Log(LogLevel.Information, message, args);
- }
-
- public void LogWarning(string message, params object[] args)
- {
- Logger.Log(LogLevel.Warning, message, args);
- }
-
- public void LogError(string message, params object[] args)
- {
- Logger.Log(LogLevel.Error, message, args);
- }
-
- public void LogCritical(string message, params object[] args)
- {
- Logger.Log(LogLevel.Critical, message, args);
- }
-
- public override string ToString()
- => $"HostedCommandContext[{Id}]";
- }
-}
diff --git a/src/CSF.Hosting/Core/Execution/Impl/HostedContextFactory.cs b/src/CSF.Hosting/Core/Execution/Impl/HostedContextFactory.cs
deleted file mode 100644
index 4afb4cd..0000000
--- a/src/CSF.Hosting/Core/Execution/Impl/HostedContextFactory.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF.Hosting
-{
- public class HostedContextFactory : IContextFactory
- {
- public ILoggerFactory LoggerFactory { get; }
-
- public HostedContextFactory(ILoggerFactory loggerFactory)
- {
- LoggerFactory = loggerFactory;
- }
-
- public virtual HostedCommandContext CreateContext()
- {
- var guid = Guid.NewGuid();
- var logger = LoggerFactory.CreateLogger($"CSF.Command.Pipeline[{Guid.NewGuid()}]");
-
- logger.LogTrace("Generating context with ID {}", guid);
-
- return new(guid, logger);
- }
-
- public void Dispose()
- {
-
- }
- }
-}
diff --git a/src/CSF.Hosting/Core/HostedCommandManager.cs b/src/CSF.Hosting/Core/HostedCommandManager.cs
deleted file mode 100644
index 2a0cb51..0000000
--- a/src/CSF.Hosting/Core/HostedCommandManager.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using CSF.Reflection;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF.Hosting
-{
- public class HostedCommandManager : CommandManager
- {
- public ILogger Logger { get; }
-
- public IContextFactory ContextFactory { get; }
-
- public HostedCommandManager(ILogger logger, IContextFactory factory, IServiceProvider services, CommandConfiguration configuration)
- : base(services, configuration)
- {
- ContextFactory = factory;
- Logger = logger;
- }
-
- public Task ExecuteAsync(params object[] args)
- {
- var context = ContextFactory.CreateContext();
-
- return base.ExecuteAsync(context, args);
- }
- }
-}
diff --git a/src/CSF.Hosting/Helpers/ServiceHelpers.cs b/src/CSF.Hosting/Helpers/ServiceHelpers.cs
index 71ed31c..2d6fcd6 100644
--- a/src/CSF.Hosting/Helpers/ServiceHelpers.cs
+++ b/src/CSF.Hosting/Helpers/ServiceHelpers.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CSF.Hosting.Helpers
+namespace CSF.Hosting.Helpers
{
internal class ServiceHelpers
{
diff --git a/src/CSF.Hosting/Hosting/Execution/IActionFactory.cs b/src/CSF.Hosting/Hosting/Execution/IActionFactory.cs
new file mode 100644
index 0000000..3712048
--- /dev/null
+++ b/src/CSF.Hosting/Hosting/Execution/IActionFactory.cs
@@ -0,0 +1,22 @@
+using CSF.Core;
+
+namespace CSF.Hosting
+{
+ public interface IActionFactory : IDisposable
+ {
+ public ValueTask CreateContextAsync(CancellationToken cancellationToken);
+
+ public ValueTask CreateArgsAsync(CancellationToken cancellationToken);
+ }
+
+ public interface IActionFactory : IActionFactory
+ where T : ICommandContext
+ {
+ public new ValueTask CreateContextAsync(CancellationToken cancellationToken);
+
+ async ValueTask IActionFactory.CreateContextAsync(CancellationToken cancellationToken)
+ {
+ return await CreateContextAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/CSF.Hosting/Hosting/Execution/Impl/HostedCommandContext.cs b/src/CSF.Hosting/Hosting/Execution/Impl/HostedCommandContext.cs
new file mode 100644
index 0000000..90ccc58
--- /dev/null
+++ b/src/CSF.Hosting/Hosting/Execution/Impl/HostedCommandContext.cs
@@ -0,0 +1,42 @@
+using CSF.Core;
+using Microsoft.Extensions.Logging;
+
+namespace CSF.Hosting
+{
+#pragma warning disable CA2254 // Template should be a static expression
+ public class HostedCommandContext(ILogger logger) : CommandContext
+ {
+ public ILogger Logger { get; } = logger;
+
+ public override void LogTrace(string message, params object[] args)
+ {
+ Logger.Log(logLevel: LogLevel.Trace, message: message, args: args);
+ }
+
+ public override void LogDebug(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Debug, message, args);
+ }
+
+ public override void LogInformation(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Information, message, args);
+ }
+
+ public override void LogWarning(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Warning, message, args);
+ }
+
+ public override void LogError(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Error, message, args);
+ }
+
+ public override void LogCritical(string message, params object[] args)
+ {
+ Logger.Log(LogLevel.Critical, message, args);
+ }
+ }
+#pragma warning restore CA2254 // Template should be a static expression
+}
diff --git a/src/CSF.Hosting/Core/Execution/Impl/HostedModuleBase.cs b/src/CSF.Hosting/Hosting/Execution/Impl/HostedModuleBase.cs
similarity index 59%
rename from src/CSF.Hosting/Core/Execution/Impl/HostedModuleBase.cs
rename to src/CSF.Hosting/Hosting/Execution/Impl/HostedModuleBase.cs
index 96f8f23..ca26279 100644
--- a/src/CSF.Hosting/Core/Execution/Impl/HostedModuleBase.cs
+++ b/src/CSF.Hosting/Hosting/Execution/Impl/HostedModuleBase.cs
@@ -1,9 +1,5 @@
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using CSF.Core;
+using Microsoft.Extensions.Logging;
namespace CSF.Hosting
{
diff --git a/src/CSF.Hosting/Hosting/HostedCommandManager.cs b/src/CSF.Hosting/Hosting/HostedCommandManager.cs
new file mode 100644
index 0000000..e28a894
--- /dev/null
+++ b/src/CSF.Hosting/Hosting/HostedCommandManager.cs
@@ -0,0 +1,72 @@
+using CSF.Core;
+using CSF.Helpers;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace CSF.Hosting
+{
+ public class HostedCommandManager : CommandManager, IHostedService
+ {
+ public ILogger Logger { get; }
+
+ public IActionFactory ActionFactory { get; }
+
+ public HostedCommandManager(ILogger logger, IActionFactory factory, IServiceProvider services, CommandConfiguration configuration)
+ : base(services, configuration)
+ {
+ ActionFactory = factory;
+ Logger = logger;
+ }
+
+ public async Task ExecuteAsync(object[] args, CancellationToken cancellationToken)
+ {
+ var context = await ActionFactory.CreateContextAsync(cancellationToken).ConfigureAwait(false);
+
+ await ExecuteAsync(context, args, cancellationToken);
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ _ = RunAsync(cancellationToken);
+
+ return Task.CompletedTask;
+ }
+
+ internal async Task RunAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ while (cancellationToken.IsCancellationRequested)
+ {
+ var args = await ActionFactory.CreateArgsAsync(cancellationToken).ConfigureAwait(false);
+
+ if (args == null)
+ {
+ ThrowHelpers.ArgMissing(args);
+ }
+
+ if (args.Length == 0)
+ {
+ ThrowHelpers.ArgMissing(args);
+ }
+
+ await ExecuteAsync(args, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch
+ {
+ // WIP
+ }
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+
+ }
+ }
+}
diff --git a/src/CSF.Hosting/Core/HostedCommandManagerResolver.cs b/src/CSF.Hosting/Hosting/HostedCommandManagerResolver.cs
similarity index 50%
rename from src/CSF.Hosting/Core/HostedCommandManagerResolver.cs
rename to src/CSF.Hosting/Hosting/HostedCommandManagerResolver.cs
index d3dc957..87be036 100644
--- a/src/CSF.Hosting/Core/HostedCommandManagerResolver.cs
+++ b/src/CSF.Hosting/Hosting/HostedCommandManagerResolver.cs
@@ -1,39 +1,33 @@
using CSF.Core;
+using CSF.Helpers;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace CSF.Hosting
{
public static class HostedCommandManagerResolver
{
- public static IHostBuilder WithCommands(this IHostBuilder builder, [DisallowNull] Action configureDelegate)
+ public static IHostBuilder WithCommands(this IHostBuilder builder, [DisallowNull] Action configureDelegate)
+ where TFactory : class, IActionFactory
{
- return builder.WithCommands(configureDelegate);
- }
-
- public static IHostBuilder WithCommands(this IHostBuilder builder, [DisallowNull] Action configureDelegate)
- where T : HostedCommandManager
- {
- return builder.WithCommands(configureDelegate);
+ return builder.WithCommands(configureDelegate);
}
public static IHostBuilder WithCommands(this IHostBuilder builder, [DisallowNull] Action configureDelegate)
- where TManager : HostedCommandManager where TFactory : class, IContextFactory
+ where TManager : HostedCommandManager where TFactory : class, IActionFactory
{
builder.ConfigureServices((context, services) =>
{
var config = new CommandConfiguration();
-
configureDelegate(context, config);
- services.WithCommands(config);
+ services.ModulesAddTransient(config);
+
+ services.TryAddSingleton(config);
+ services.TryAddSingleton();
services.AddScoped();
});
diff --git a/src/CSF.Tests.Console/Complex/ComplexType.cs b/src/CSF.Tests.Console/Complex/ComplexType.cs
index ea73b5e..42486f4 100644
--- a/src/CSF.Tests.Console/Complex/ComplexType.cs
+++ b/src/CSF.Tests.Console/Complex/ComplexType.cs
@@ -1,4 +1,6 @@
-namespace CSF.Tests.Complex
+using CSF.Core;
+
+namespace CSF.Tests.Complex
{
public class ComplexType
{
diff --git a/src/CSF.Tests.Console/Complex/ComplexerType.cs b/src/CSF.Tests.Console/Complex/ComplexerType.cs
index 0fd9dee..fd88feb 100644
--- a/src/CSF.Tests.Console/Complex/ComplexerType.cs
+++ b/src/CSF.Tests.Console/Complex/ComplexerType.cs
@@ -1,4 +1,6 @@
-namespace CSF.Tests.Complex
+using CSF.Core;
+
+namespace CSF.Tests.Complex
{
public class ComplexerType
{
diff --git a/src/CSF.Tests.Console/Modules/AsyncModule.cs b/src/CSF.Tests.Console/Modules/AsyncModule.cs
index c2b2915..6ecc9de 100644
--- a/src/CSF.Tests.Console/Modules/AsyncModule.cs
+++ b/src/CSF.Tests.Console/Modules/AsyncModule.cs
@@ -1,4 +1,6 @@
-namespace CSF.Tests.Console.Modules
+using CSF.Core;
+
+namespace CSF.Tests.Console.Modules
{
public sealed class AsyncModule : ModuleBase
{
@@ -7,13 +9,13 @@ public async Task AsyncRunDelayed()
{
await Task.Delay(5000);
- Respond("Success. (Delayed).");
+ System.Console.WriteLine("Success. (Delayed).");
}
[Command("direct")]
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task AsyncRunDirect()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
- => Respond("Success. (Direct).");
+ => System.Console.WriteLine("Success. (Direct).");
}
}
diff --git a/src/CSF.Tests.Console/Modules/Module.cs b/src/CSF.Tests.Console/Modules/Module.cs
index f2de5d2..9d45bdb 100644
--- a/src/CSF.Tests.Console/Modules/Module.cs
+++ b/src/CSF.Tests.Console/Modules/Module.cs
@@ -1,4 +1,5 @@
-using CSF.Tests.Complex;
+using CSF.Core;
+using CSF.Tests.Complex;
namespace CSF.Tests
{
@@ -8,63 +9,63 @@ public class Module : ModuleBase
[Priority(1)]
public void Priority1(bool optional = true)
{
- Respond($"Success: {Command.Priority}");
+ System.Console.WriteLine($"Success: {Command.Priority}");
}
[Command("priority")]
[Priority(2)]
public Task Priority2(bool optional = false)
{
- Respond($"Success: {Command.Priority}");
+ System.Console.WriteLine($"Success: {Command.Priority}");
return Task.CompletedTask;
}
[Command("remainder")]
public void Remainder([Remainder] string values)
{
- Respond($"Success: {values}");
+ System.Console.WriteLine($"Success: {values}");
}
[Command("time")]
public void TimeOnly(TimeOnly time)
{
- Respond($"Success: {time}");
+ System.Console.WriteLine($"Success: {time}");
}
[Command("multiple")]
public void Test(bool truee, bool falsee)
{
- Respond($"Success: {truee}, {falsee}");
+ System.Console.WriteLine($"Success: {truee}, {falsee}");
}
[Command("multiple")]
public void Test(int i1, int i2)
{
- Respond($"Success: {i1}, {i2}");
+ System.Console.WriteLine($"Success: {i1}, {i2}");
}
[Command("optional")]
public void Test(int i = 0, string str = "")
{
- Respond($"Success: {i}, {str}");
+ System.Console.WriteLine($"Success: {i}, {str}");
}
[Command("nullable")]
public void Nullable(long? l)
{
- Respond($"Success: {l}");
+ System.Console.WriteLine($"Success: {l}");
}
[Command("complex")]
public void Complex([Complex] ComplexType complex)
{
- Respond($"({complex.X}, {complex.Y}, {complex.Z}) {complex.Complexer}: {complex.Complexer.X}, {complex.Complexer.Y}, {complex.Complexer.Z}");
+ System.Console.WriteLine($"({complex.X}, {complex.Y}, {complex.Z}) {complex.Complexer}: {complex.Complexer.X}, {complex.Complexer.Y}, {complex.Complexer.Z}");
}
[Command("complexnullable")]
public void Complex([Complex] ComplexerType? complex)
{
- Respond($"({complex?.X}, {complex?.Y}, {complex?.Z})");
+ System.Console.WriteLine($"({complex?.X}, {complex?.Y}, {complex?.Z})");
}
[Group("nested")]
@@ -73,37 +74,37 @@ public class NestedModule : ModuleBase
[Command("multiple")]
public void Test(bool truee, bool falsee)
{
- Respond($"Success: {truee}, {falsee}");
+ System.Console.WriteLine($"Success: {truee}, {falsee}");
}
[Command("multiple")]
public void Test(int i1, int i2)
{
- Respond($"Success: {i1}, {i2}");
+ System.Console.WriteLine($"Success: {i1}, {i2}");
}
[Command("optional")]
public void Test(int i = 0, string str = "")
{
- Respond($"Success: {i}, {str}");
+ System.Console.WriteLine($"Success: {i}, {str}");
}
[Command("nullable")]
public void Nullable(long? l)
{
- Respond($"Success: {l}");
+ System.Console.WriteLine($"Success: {l}");
}
[Command("complex")]
public void Complex([Complex] ComplexType complex)
{
- Respond($"({complex.X}, {complex.Y}, {complex.Z}) {complex.Complexer}: {complex.Complexer.X}, {complex.Complexer.Y}, {complex.Complexer.Z}");
+ System.Console.WriteLine($"({complex.X}, {complex.Y}, {complex.Z}) {complex.Complexer}: {complex.Complexer.X}, {complex.Complexer.Y}, {complex.Complexer.Z}");
}
[Command("complexnullable")]
public void Complex([Complex] ComplexerType? complex)
{
- Respond($"({complex?.X}, {complex?.Y}, {complex?.Z})");
+ System.Console.WriteLine($"({complex?.X}, {complex?.Y}, {complex?.Z})");
}
}
}
diff --git a/src/CSF.Tests.Console/Program.cs b/src/CSF.Tests.Console/Program.cs
index 68ba3ad..4ae2010 100644
--- a/src/CSF.Tests.Console/Program.cs
+++ b/src/CSF.Tests.Console/Program.cs
@@ -1,41 +1,19 @@
-//using CSF;
-//using Microsoft.Extensions.DependencyInjection;
-
-//var collection = new ServiceCollection()
-// .WithCommandManager();
-
-//var services = collection.BuildServiceProvider();
-
-//var framework = services.GetRequiredService();
-
-//var delayed = new CommandContext("delayed");
-//var direct = new CommandContext("direct");
-
-//framework.ExecuteAsync(delayed);
-//framework.ExecuteAsync(direct);
-
-//await Task.Delay(Timeout.Infinite);
+using CSF.Core;
+using CSF.Parsing;
+using Microsoft.Extensions.DependencyInjection;
-//while (true)
-//{
-// var context = new CommandContext(Console.ReadLine()!);
+var collection = new ServiceCollection()
+ .WithCommands(_ => { });
-// var result = framework.ExecuteAsync(context);
+var services = collection.BuildServiceProvider();
-// if (result.Failed(out var failure))
-// Console.WriteLine(failure.Exception);
-//}
-
-using CSF.Parsing;
+var framework = services.GetRequiredService();
var parser = new StringParser();
while (true)
{
- var text = Console.ReadLine();
-
- var value = parser.Parse(text);
+ var input = parser.Parse(Console.ReadLine()!);
- foreach (var item in value)
- Console.WriteLine("-> " + item);
+ await framework.ExecuteAsync(null, input);
}
\ No newline at end of file
diff --git a/src/CSF.Tests.Hosting/Factory.cs b/src/CSF.Tests.Hosting/Factory.cs
new file mode 100644
index 0000000..7207d6d
--- /dev/null
+++ b/src/CSF.Tests.Hosting/Factory.cs
@@ -0,0 +1,53 @@
+using CSF.Helpers;
+using CSF.Hosting;
+using CSF.Parsing;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSF.Tests.Hosting
+{
+ public class Factory : IActionFactory
+ {
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly StringParser _stringParser;
+
+ public Factory(ILoggerFactory loggerFactory)
+ {
+ _loggerFactory = loggerFactory;
+ _stringParser = new StringParser();
+ }
+
+ public ValueTask CreateArgsAsync(CancellationToken cancellationToken)
+ {
+ var input = Console.ReadLine();
+
+ var args = _stringParser.Parse(input);
+
+ if (args.Length == 0)
+ {
+ ThrowHelpers.ArgMissing(args);
+ }
+
+ return ValueTask.FromResult(args);
+ }
+
+ public ValueTask CreateContextAsync(CancellationToken cancellationToken)
+ {
+ var guid = Guid.NewGuid();
+ var logger = _loggerFactory.CreateLogger($"CSF.Command.Pipeline[{Guid.NewGuid()}]");
+
+ logger.LogTrace("Generating context with ID {}", guid);
+
+ return ValueTask.FromResult(new HostedCommandContext(logger));
+ }
+
+ public void Dispose()
+ {
+
+ }
+ }
+}
diff --git a/src/CSF.Tests.Hosting/Module.cs b/src/CSF.Tests.Hosting/Module.cs
index 2dc2875..df0a874 100644
--- a/src/CSF.Tests.Hosting/Module.cs
+++ b/src/CSF.Tests.Hosting/Module.cs
@@ -1,6 +1,9 @@
-namespace CSF.Tests.Hosting
+using CSF.Core;
+using CSF.Hosting;
+
+namespace CSF.Tests.Hosting
{
- public sealed class Module : ModuleBase