Skip to content

Commit

Permalink
Updated setup and build
Browse files Browse the repository at this point in the history
  • Loading branch information
jakenuts committed Dec 31, 2023
1 parent de12952 commit 9b0aed8
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 28 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ jobs:
runs-on: ubuntu-latest

env:
SOLUTION: ./src/Spectre.Console.Extensions.Hosting.sln
PROJECT: ./src/Spectre.Console.Extensions.Hosting/Spectre.Console.Extensions.Hosting.csproj
SOLUTION: ./src/Community.Extensions.Spectre.Cli.Hosting.sln
PROJECT: ./src/Community.Extensions.Spectre.Cli.Hosting/Community.Extensions.Spectre.Cli.Hosting.csproj
CONFIG: Release

steps:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
{
"profiles": {
"Sample": {
"Sample:Help": {
"commandName": "Project",
//"commandLineArgs": "You -p Sadie"
//"commandLineArgs": "other stuff"
"commandLineArgs": "--help"
},
"Sample:Hello": {
"commandName": "Project",
"commandLineArgs": "You -p Sadie"
},
"Sample:Other": {
"commandName": "Project",
"commandLineArgs": "other stuff"
},
"Interactive": {
"commandName": "Executable",
"executablePath": "pwsh.exe",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Community.Extensions.Spectre.Cli.Hosting.Internal;
/// A typed registration class for commands with their types and name
/// </summary>
/// <param name="Name"></param>
/// <param name="CommandConfigurator"></param>
/// <typeparam name="TCommand"></typeparam>
public record CommandRegistration<TCommand>(string Name, Action<ICommandConfigurator>? CommandConfigurator = null)
: CommandRegistration(typeof(TCommand), Name) where TCommand : class, ICommand
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,31 +35,30 @@ public ITypeResolver Build()

public void Register(Type service, Type implementation)
{
var descriptor = ServiceDescriptor.Singleton(service, implementation);

if (ContainsMatchingServiceDescriptor(_outerCollection, descriptor))
if (HostServicesAlreadyContainServiceType(service))
{
_log.LogTrace($"✔️[Inner-Provider] Skipping duplicate registration of {implementation}");
return;
}

_log.LogTrace($"✨[Inner-Provider] Register {implementation}");

var descriptor = ServiceDescriptor.Singleton(service, implementation);
_internalBuilder.Add(descriptor);
}

public void RegisterInstance(Type service, object implementation)
{
var descriptor = ServiceDescriptor.Singleton(service, implementation);

if (ContainsMatchingServiceDescriptor(_outerCollection, descriptor))
if (HostServicesAlreadyContainServiceType(service))
{
_log.LogTrace($"✔️[Inner-Provider] Skipping duplicate registration of {implementation}");
return;
}

_log.LogTrace($"✨[Inner-Provider] RegisterInstance {implementation}");

var descriptor = ServiceDescriptor.Singleton(service, implementation);

_internalBuilder.Add(descriptor);
}

Expand All @@ -76,19 +75,17 @@ public void RegisterLazy(Type service, Func<object> func)
}

/// <summary>
/// Easier to implement with linq but this is the frameworks internal implementation
/// Checks the service collection for any registered services matching the type
/// </summary>
/// <param name="collection"></param>
/// <param name="descriptor"></param>
/// <param name="serviceType"></param>
/// <returns></returns>
private static bool ContainsMatchingServiceDescriptor(IServiceCollection collection, ServiceDescriptor descriptor)
private bool HostServicesAlreadyContainServiceType(Type serviceType)
{
var count = collection.Count;
var count = _outerCollection.Count;

for (var i = 0; i < count; i++)
{
if (collection[i].ServiceType == descriptor.ServiceType
&& collection[i].ServiceKey == descriptor.ServiceKey)
if (_outerCollection[i].ServiceType == serviceType)
{
// Already added
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,74 @@ namespace Community.Extensions.Spectre.Cli.Hosting.Internal;

internal sealed class CustomTypeResolver : ITypeResolver, IDisposable
{
private readonly IServiceProvider _outerProvider;

private readonly IServiceProvider _serviceProvider;

#region Services Registered by the Host
private readonly IServiceProvider _hostServiceRootProvider;
private IServiceScope? _hostServiceScope;
private IServiceProvider HostServiceProvider => _hostServiceScope?.ServiceProvider ?? _hostServiceRootProvider;
#endregion

#region Internal Services Registered by the Spectre.Console.Cli
private readonly IServiceProvider _internalServicesProvider;
private IServiceProvider InternalServiceProvider => _internalServicesProvider;
#endregion

private readonly ILogger<CustomTypeResolver> _log;

public CustomTypeResolver(IServiceProvider internalProvider, IServiceProvider outerProvider)
/// <summary>
/// Allows for resolving types from the host service provider or from the internal
/// services registered by Spectre.Console.Cli.
/// </summary>
/// <param name="internalProvider"></param>
/// <param name="hostServiceProvider"></param>
/// <exception cref="ArgumentNullException"></exception>
public CustomTypeResolver(IServiceProvider internalProvider, IServiceProvider hostServiceProvider)
{
_outerProvider = outerProvider;
_serviceProvider = internalProvider ?? throw new ArgumentNullException(nameof(internalProvider));

_log = _outerProvider.GetRequiredService<ILogger<CustomTypeResolver>>();
_hostServiceRootProvider = hostServiceProvider;
_hostServiceScope = _hostServiceRootProvider.CreateScope();

_internalServicesProvider = internalProvider ?? throw new ArgumentNullException(nameof(internalProvider));
_log = HostServiceProvider.GetRequiredService<ILogger<CustomTypeResolver>>();
}

/// <summary>
/// Cleans up the internal service provider and host service scope
/// </summary>
public void Dispose()
{
if (_serviceProvider is IDisposable disposable)
if (_internalServicesProvider is IDisposable disposable)
{
disposable.Dispose();
}

// Dispose of the outer service scope
_hostServiceScope?.Dispose();
_hostServiceScope = null;
}

/// <summary>
/// Called by Spectre.Console.Cli to resolve a type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public object? Resolve(Type? type)
{
if (type == null)
{
return null;
}

var service = _outerProvider.GetService(type);
// First try to resolve from the standard host service provider
var service = HostServiceProvider.GetService(type);

if (service == null)
{
service = _serviceProvider.GetService(type);
// Next try to resolve from the service provider associated with internal service
service = InternalServiceProvider.GetService(type);

if (service == null)
{
// Last fall back on activator
service = Activator.CreateInstance(type);

_log.LogDebug($"✨[Activator-Provider] Returned {type.FullName}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static class SpectreConsoleHostBuilderExtensions
{
/// <summary>
/// Adds a command and it's options to the service collection. Also registers the command
/// to be added & configured during the UseSpectreConsole call.
/// to be added &amp; configured during the UseSpectreConsole call.
/// </summary>
/// <typeparam name="TCommand"></typeparam>
/// <param name="services"></param>
Expand Down

0 comments on commit 9b0aed8

Please sign in to comment.