8000 Global improvements for testing & argument parsing by csmir · Pull Request #12 · csmir/Commands.NET · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Global improvements for testing & argument parsing #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasional 8000 ly send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,4 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/Visual Studio 2022/Visualizers
/src/Commands.Samples/Commands.Samples.Console/Properties
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ using Commands;

var command = Command.From(() => "Hello world!", "greet");

var collection = ComponentCollection.From(command).ToCollection();
var collection = ComponentProvider.From(command).ToProvider();

await collection.Execute(new ConsoleCallerContext(args));

Expand All @@ -75,7 +75,7 @@ var mathCommands = CommandGroup.From("math")
"divide", "div")
);

var collection = ComponentCollection.From(mathCommands).ToCollection();
var collection = ComponentProvider.From(mathCommands).ToProvider();

await collection.Execute(new ConsoleCallerContext(args));

Expand Down Expand Up @@ -106,7 +106,7 @@ public class HelpModule : CommandModule

...

var collection = ComponentCollection.From(mathCommands).AddType<HelpModule>().ToCollection();
var collection = ComponentProvider.From(mathCommands).AddType<HelpModule>().ToProvider();

await collection.Execute(new ConsoleCallerContext(args));

Expand All @@ -125,10 +125,10 @@ using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection()
.AddSingleton<MyService>()
.AddSingleton<ComponentCollection>(ComponentCollection.From(mathCommands).AddType<HelpModule>().ToCollection());
.AddSingleton<ComponentProvider>(ComponentProvider.From(mathCommands).AddType<HelpModule>().ToProvider());
.BuildServiceProvider();

var collection = services.GetRequiredService<ComponentCollection>();
var collection = services.GetRequiredService<ComponentProvider>();

await collection.Execute(new ConsoleCallerContext(args), new CommandOptions() { Services = services });
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public sealed class HostedCommandOptions
/// This behavior drastically changes execution flow, and as such, there are a few things to consider when using it:
/// <list type="bullet">
/// <item>
/// The end-user must provide an implementation of <see cref="ResultHandler"/> to the <see cref="ComponentCollection"/> that is used to execute the command, in order to handle the result of the command.
/// The end-user must provide an implementation of <see cref="ResultHandler"/> to the <see cref="ComponentProvider"/> that is used to execute the command, in order to handle the result of the command.
/// </item>
/// <item>
/// Objects, specifically those scoped to more than a single command must be made thread-safe, meaning they must be able to handle multiple requests at once.
Expand Down
14 changes: 7 additions & 7 deletions src/Commands.Hosting/Hosting/HostUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,31 @@ public static IHostBuilder ConfigureComponents(this IHostBuilder builder)

/// <inheritdoc cref="ConfigureComponents(IHostBuilder)"/>
/// <param name="builder"></param>
/// <param name="configureAction">An action responsible for configuring a newly created instance of <see cref="ComponentCollectionProperties"/> in preparation for building an implementation of <see cref="IExecutionProvider"/> to execute commands with.</param>
/// <param name="configureAction">An action responsible for configuring a newly created instance of <see cref="ComponentProviderProperties"/> in preparation for building an implementation of <see cref="IExecutionProvider"/> to execute commands with.</param>
/// <returns>The same <see cref="IHostBuilder"/> for call-chaining.</returns>
public static IHostBuilder ConfigureComponents(this IHostBuilder builder, Action<ComponentCollectionProperties> configureAction)
public static IHostBuilder ConfigureComponents(this IHostBuilder builder, Action<ComponentProviderProperties> configureAction)
=> ConfigureComponents(builder, (ctx, props) => configureAction(props));

/// <inheritdoc cref="ConfigureComponents(IHostBuilder)"/>
/// <param name="builder"></param>
/// <param name="configureAction">An action responsible for configuring a newly created instance of <see cref="ComponentCollectionProperties"/> in preparation for building an implementation of <see cref="IExecutionProvider"/> to execute commands with.</param>
/// <param name="configureAction">An action responsible for configuring a newly created instance of <see cref="ComponentProviderProperties"/> in preparation for building an implementation of <see cref="IExecutionProvider"/> to execute commands with.</param>
/// <returns>The same <see cref="IHostBuilder"/> for call-chaining.</returns>
public static IHostBuilder ConfigureComponents(this IHostBuilder builder, Action<HostBuilderContext, ComponentCollectionProperties> configureAction)
public static IHostBuilder ConfigureComponents(this IHostBuilder builder, Action<HostBuilderContext, ComponentProviderProperties> configureAction)
=> ConfigureComponents<CommandExecutionFactory>(builder, configureAction);

/// <inheritdoc cref="ConfigureComponents(IHostBuilder)"/>
/// <typeparam name="TFactory">The implementation of <see cref="IExecutionFactory"/> to consider the factory for executing commands using this host as the lifetime.</typeparam>
/// <param name="builder"></param>
/// <param name="configureAction">An action responsible for configuring a newly created instance of <see cref="ComponentCollectionProperties"/> in preparation for building an implementation of <see cref="IExecutionProvider"/> to execute commands with.</param>
/// <param name="configureAction">An action responsible for configuring a newly created instance of <see cref="ComponentProviderProperties"/> in preparation for building an implementation of <see cref="IExecutionProvider"/> to execute commands with.</param>
/// <returns>The same <see cref="IHostBuilder"/> for call-chaining.</returns>
public static IHostBuilder ConfigureComponents<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactory>
(this IHostBuilder builder, Action<HostBuilderContext, ComponentCollectionProperties> configureAction)
(this IHostBuilder builder, Action<HostBuilderContext, ComponentProviderProperties> configureAction)
where TFactory : CommandExecutionFactory
{
Assert.NotNull(builder, nameof(builder));
Assert.NotNull(configureAction, nameof(configureAction));

var properties = new ComponentCollectionProperties();
var properties = new ComponentProviderProperties();
var services = builder.ConfigureServices((ctx, services) =>
{
configureAction(ctx, properties);
Expand Down
16 changes: 8 additions & 8 deletions src/Commands.Hosting/Hosting/ServiceUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ public static class ServiceUtilities
/// Additionally, it provides a factory based execution mechanism for commands, implementing a singleton <see cref="IExecutionFactory"/>, scoped <see cref="IExecutionContext"/> and transient <see cref="ICallerContextAccessor{TCaller}"/>.
/// </remarks>
/// <param name="services"></param>
/// <param name="configureAction">An action responsible for configuring a newly created instance of <see cref="ComponentCollectionProperties"/> in preparation for building an implementation of <see cref="IExecutionProvider"/> to execute commands with.</param>
/// <param name="configureAction">An action responsible for configuring a newly created instance of <see cref="ComponentProviderProperties"/> in preparation for building an implementation of <see cref="IExecutionProvider"/> to execute commands with.</param>
/// <returns>The same <see cref="IServiceCollection"/> for call-chaining.</returns>
public static IServiceCollection AddComponentCollection<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactory>
(this IServiceCollection services, Action<ComponentCollectionProperties> configureAction)
(this IServiceCollection services, Action<ComponentProviderProperties> configureAction)
where TFactory : class, IExecutionFactory
{
Assert.NotNull(services, nameof(services));
Assert.NotNull(configureAction, nameof(configureAction));

var properties = new ComponentCollectionProperties();
var properties = new ComponentProviderProperties();

configureAction(properties);

Expand All @@ -34,7 +34,7 @@ public static class ServiceUtilities

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static IServiceCollection AddComponentCollection<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactory>
(IServiceCollection services, ComponentCollectionProperties properties)
(IServiceCollection services, ComponentProviderProperties properties)
where TFactory : class, IExecutionFactory
{
if (services.Contains<IExecutionFactory>())
Expand All @@ -52,12 +52,12 @@ public static class ServiceUtilities
services.AddScoped<IExecutionContext, ExecutionContext>();
services.AddTransient(typeof(ICallerContextAccessor<>), typeof(CallerContextAccessor<>));

var collectionDescriptor = ServiceDescriptor.Singleton<IExecutionProvider, ComponentCollection>(x =>
var collectionDescriptor = ServiceDescriptor.Singleton<IExecutionProvider, ComponentProvider>(x =>
{
// Implement global result handler to dispose of the execution scope. This must be done last, even if the properties are mutated anywhere before.
properties.AddResultHandler(new ExecutionScopeResolver());

var collection = properties.ToCollection();
var collection = properties.ToProvider();

return collection;
});
Expand All @@ -66,10 +66,10 @@ public static class ServiceUtilities
{
var provider = x.GetRequiredService<IExecutionProvider>();

if (provider is ComponentCollection collection)
if (provider is ComponentProvider collection)
return collection.Configuration;

throw new NotSupportedException($"The component collection is not available in the current context. Ensure that you are configuring an instance of {nameof(ComponentCollection)}.");
throw new NotSupportedException($"The component collection is not available in the current context. Ensure that you are configuring an instance of {nameof(ComponentProvider)}.");
});

services.Add(collectionDescriptor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Commands.Samples;

[RequireContext<TestContext>]
public sealed class TestModule : CommandModule<TestContext>
[RequireContext<ConsoleCallerContext>]
public sealed class TestModule : CommandModule<ConsoleCallerContext>
{
[Name("testcommand")]
[Test]
Expand Down
19 changes: 10 additions & 9 deletions src/Commands.Samples/Commands.Samples.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
var results = ResultHandler.For<SampleContext>()
.AddDelegate((c, e, s) => c.Respond(e));

var components = new ComponentCollectionProperties()
var components = new ComponentProviderProperties()
.WithConfiguration(configuration)
.AddResultHandler(results)
.ToCollection();
.ToProvider();

var tests = new TestCollectionProperties()
.AddCommands([.. components.GetCommands()])
.ToCollection();
var tests = components.GetCommands().Select(x => TestProvider.From(x).ToProvider());

var testEvaluation = await tests.Execute((str) => new TestContext(str));
foreach (var test in tests)
{
var result = await test.Test(x => new ConsoleCallerContext(x));

if (testEvaluation.Count(x => 10000 x.Success) == tests.Count)
Console.WriteLine("All tests ran successfully.");
if (result.Any(x => !x.Success))
throw new InvalidOperationException($"A command test failed to evaluate to success. Command: {test.Command}. Test: {result.FirstOrDefault(x => !x.Success).Test}");
}

while (true)
await components.Execute(new SampleContext(username: "Peter", args: Console.ReadLine()));
await components.Execute(new SampleContext(username: "Peter", args: Console.ReadLine()));
4 changes: 2 additions & 2 deletions src/Commands.Samples/Commands.Samples.Core/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
Command.From(Divide, "divide", "div")
);

var components = ComponentCollection.From(exit, mathCommands)
var components = ComponentProvider.From(exit, mathCommands)
.AddComponentType<HelpModule>()
.ToCollection();
.ToProvider();

await components.Execute(new ConsoleCallerContext(args));

Expand Down
2 changes: 1 addition & 1 deletion src/Commands.Samples/Commands.Samples.FSharp/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
open Commands.Samples;
open System

let components = new ComponentCollection()
let components = new ComponentProvider()

printf "Added %i components." (components.AddRange(typeof<FSharpModule>.Assembly.GetExportedTypes()))

Expand Down
2 changes: 1 addition & 1 deletion src/Commands.Tests/Commands.Tests.Aot/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Commands;
using Commands.Tests;

var manager = new ComponentCollection(new DelegateResultHandler<ConsoleCallerContext>((c, e, s) => c.Respond(e)))
var manager = new ComponentProvider(new DelegateResultHandler<ConsoleCallerContext>((c, e, s) => c.Respond(e)))
{
new CommandGroup(typeof(Module), configuration: new ComponentConfiguration()),
new CommandGroup("commandgroup")
Expand Down
12 changes: 6 additions & 6 deletions src/Commands.Tests/Commands.Tests.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Commands.Tests;

public class BenchmarkCallerContext(string? input) : AsyncCallerContext
{
public override ArgumentDictionary Arguments { get; } = ArgumentDictionary.From(input);
public override ArgumentDictionary Arguments { get; } = ArgumentDictionary.FromString(input);

public override Task Respond(object? response)
=> Task.CompletedTask;
Expand All @@ -27,18 +27,18 @@ public static void Command3() { }
[MemoryDiagnoser]
public class Program
{
private static readonly ArgumentDictionary _args = ArgumentDictionary.From("command");
private static readonly ComponentCollection _components = ComponentCollection.From()
private static readonly ArgumentDictionary _args = ArgumentDictionary.FromString("command");
private static readonly ComponentProvider _components = ComponentProvider.From()
.AddComponentType<CreationAnalysisModule>()
.AddComponent(Command.From(() => { }, "command"))
.ToCollection();
.ToProvider();

static void Main()
=> BenchmarkRunner.Run<Program>();

[Benchmark]
public void CreateArguments()
=> ArgumentDictionary.From("command");
=> ArgumentDictionary.FromString("command");

[Benchmark]
public void FindCommands()
Expand All @@ -56,7 +56,7 @@ public Task RunCommandNonBlocking()
});

[Benchmark]
public ComponentCollection CollectionCreate()
public ComponentProvider CollectionCreate()
=> [];

[Benchmark]
Expand Down
16 changes: 9 additions & 7 deletions src/Commands.Tests/Commands.Tests.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Commands;
using Commands.Testing;

var components = new ComponentCollectionProperties()
var components = new ComponentProviderProperties()
.AddComponentTypes(typeof(Program).Assembly.GetExportedTypes())
.AddResultHandler(ResultHandler.From<ICallerContext>((c, e, s) => c.Respond(e)))
.AddComponent(
Expand All @@ -11,15 +11,17 @@
c.Respond(command);

}, "help"))
.ToCollection();
.ToProvider();

var tests = TestCollection.From([.. components.GetCommands()])
.ToCollection();
var tests = components.GetCommands().Select(x => TestProvider.From(x).ToProvider());

var results = await tests.Execute((str) => new TestContext(str));
foreach (var test in tests)
{
var result = await test.Test(x => new ConsoleCallerContext(x));

if (results.Count(x => x.Success) == tests.Count)
Console.WriteLine("All tests ran succesfully.");
if (result.Any(x => !x.Success))
throw new InvalidOperationException($"A command test failed to evaluate to success. Command: {test.Command}. Test: {result.FirstOrDefault(x => !x.Success).Test}");
}

while (true)
await components.Execute(new ConsoleCallerContext(Console.ReadLine()));
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Commands;

/// <summary>
/// Defines mechanisms for executing commands based on a set of arguments.
/// Defines a mechanism for executing commands based on a set of arguments.
/// </summary>
public interface IExecutionProvider : IComponentCollection
{
Expand Down
6 changes: 3 additions & 3 deletions src/Commands/Core/Components/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ public async ValueTask<IResult> Run<TContext>(TContext caller, CommandOptions op

object?[] parameters;

if (!HasParameters && args.AvailableLength == 0)
if (!HasParameters && args.RemainingLength == 0)
parameters = [];
else if (MaxLength == args.AvailableLength || (MaxLength <= args.AvailableLength && HasRemainder) || (MaxLength > args.AvailableLength && MinLength <= args.AvailableLength))
else if (MaxLength == args.RemainingLength || (MaxLength <= args.RemainingLength && HasRemainder) || (MaxLength > args.RemainingLength && MinLength <= args.RemainingLength))
{
var arguments = await ComponentUtilities.Parse(this, caller, args, options).ConfigureAwait(false);

Expand All @@ -184,7 +184,7 @@ public async ValueTask<IResult> Run<TContext>(TCont DCCA ext caller, CommandOptions op
}
}
else
return ParseResult.FromError(new CommandOutOfRangeException(this, args.AvailableLength));
return ParseResult.FromError(new CommandOutOfRangeException(this, args.RemainingLength));

if (!options.SkipConditions)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Commands/Core/Components/ComponentCollectionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ private List<IComponent> FilterComponents(IEnumerable<IComponent> components)
if (_items.Contains(component))
continue;

if (this is ComponentCollection manager)
if (this is ComponentProvider manager)
{
// When a component is not searchable it means it has no names. Between a manager and a group, a different restriction applies to how this should be done.
if (!component.IsSearchable)
Expand All @@ -261,7 +261,7 @@ private List<IComponent> FilterComponents(IEnumerable<IComponent> components)
// Anything added to a group should be considered nested.
// Because of the nature of this design, we want to avoid folding anything but top level. This means that nested groups must be named.
if (component is not Command)
throw new InvalidOperationException($"{nameof(CommandGroup)} instances without names can only be added to a {nameof(ComponentCollection)}.");
throw new InvalidOperationException($"{nameof(CommandGroup)} instances without names can only be added to a {nameof(ComponentProvider)}.");

discovered.Add(component);
}
Expand Down
Loading
Loading
0