Automatically register command settings

Closes #428
This commit is contained in:
Patrik Svensson 2024-02-15 21:18:13 +01:00 committed by Patrik Svensson
parent b6e0b2389a
commit fde9ee04cf
6 changed files with 132 additions and 5 deletions

View File

@ -17,6 +17,12 @@ internal static class TypeRegistrarExtensions
throw new InvalidOperationException("Command setting type cannot be null.");
}
if (command.SettingsType is { IsAbstract: false, IsClass: true })
{
// Register the settings type
registrar?.Register(command.SettingsType, command.SettingsType);
}
if (command.CommandType != null)
{
registrar?.Register(command.CommandType, command.CommandType);

View File

@ -1,6 +1,6 @@
<Project>
<PropertyGroup Label="Settings">
<LangVersion>10</LangVersion>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

View File

@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="IsExternalInit" Version="1.0.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Shouldly" Version="4.2.1" />
<PackageReference Include="Spectre.Verify.Extensions" Version="22.3.1" />
<PackageReference Include="Verify.Xunit" Version="23.2.0" />

View File

@ -0,0 +1,116 @@
#nullable enable
using Microsoft.Extensions.DependencyInjection;
namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests
{
public sealed partial class Injection
{
public sealed class Settings
{
public sealed class CustomInheritedCommand : Command<CustomInheritedCommandSettings>
{
private readonly SomeFakeDependency _dep;
public CustomInheritedCommand(SomeFakeDependency dep)
{
_dep = dep;
}
public override int Execute(CommandContext context, CustomInheritedCommandSettings settings)
{
return 0;
}
}
public sealed class SomeFakeDependency
{
}
public abstract class CustomBaseCommandSettings : CommandSettings
{
}
public sealed class CustomInheritedCommandSettings : CustomBaseCommandSettings
{
}
private sealed class CustomTypeRegistrar : ITypeRegistrar
{
private readonly IServiceCollection _services;
public CustomTypeRegistrar(IServiceCollection services)
{
_services = services;
}
public ITypeResolver Build()
{
return new CustomTypeResolver(_services.BuildServiceProvider());
}
public void Register(Type service, Type implementation)
{
_services.AddSingleton(service, implementation);
}
public void RegisterInstance(Type service, object implementation)
{
_services.AddSingleton(service, implementation);
}
public void RegisterLazy(Type service, Func<object> func)
{
_services.AddSingleton(service, provider => func());
}
}
public sealed class CustomTypeResolver : ITypeResolver
{
private readonly IServiceProvider _provider;
public CustomTypeResolver(IServiceProvider provider)
{
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
public object? Resolve(Type? type)
{
ArgumentNullException.ThrowIfNull(type);
return _provider.GetRequiredService(type);
}
}
[Fact]
public void Should_Inject_Settings()
{
static CustomTypeRegistrar BootstrapServices()
{
var services = new ServiceCollection();
services.AddSingleton<SomeFakeDependency, SomeFakeDependency>();
return new CustomTypeRegistrar(services);
}
// Given
var app = new CommandAppTester(BootstrapServices());
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<CustomBaseCommandSettings>("foo", b =>
{
b.AddCommand<CustomInheritedCommand>("bar");
});
});
// When
var result = app.Run("foo", "bar");
// Then
result.ExitCode.ShouldBe(0);
}
}
}
}

View File

@ -2,13 +2,17 @@ namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests
{
public sealed class Injection
public sealed partial class Injection
{
public sealed class FakeDependency
{
}
public sealed class InjectSettings : CommandSettings
public abstract class BaseInjectSettings : CommandSettings
{
}
public sealed class InjectSettings : BaseInjectSettings
{
public FakeDependency Fake { get; set; }

View File

@ -557,7 +557,7 @@ public sealed partial class CommandAppTests
// Then
registrar.Registrations.ContainsKey(typeof(DogSettings)).ShouldBeTrue();
registrar.Registrations[typeof(DogSettings)].Count.ShouldBe(1);
registrar.Registrations[typeof(DogSettings)].Count.ShouldBe(2);
registrar.Registrations[typeof(DogSettings)].ShouldContain(typeof(DogSettings));
}
@ -587,7 +587,7 @@ public sealed partial class CommandAppTests
// Then
registrar.Registrations.ContainsKey(typeof(DogSettings)).ShouldBeTrue();
registrar.Registrations[typeof(DogSettings)].Count.ShouldBe(1);
registrar.Registrations[typeof(DogSettings)].Count.ShouldBe(2);
registrar.Registrations[typeof(DogSettings)].ShouldContain(typeof(DogSettings));
registrar.Registrations.ContainsKey(typeof(MammalSettings)).ShouldBeTrue();
registrar.Registrations[typeof(MammalSettings)].Count.ShouldBe(1);