mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Add an overload of UseTypeActivator(...) that takes a list of added command types
This commit is contained in:
@@ -1,25 +1,21 @@
|
||||
using CliFx;
|
||||
using CliFx.Demo.Commands;
|
||||
using CliFx.Demo.Domain;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Register services
|
||||
services.AddSingleton<LibraryProvider>();
|
||||
|
||||
// Register commands
|
||||
services.AddTransient<BookCommand>();
|
||||
services.AddTransient<BookAddCommand>();
|
||||
services.AddTransient<BookRemoveCommand>();
|
||||
services.AddTransient<BookListCommand>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
return await new CliApplicationBuilder()
|
||||
.SetDescription("Demo application showcasing CliFx features.")
|
||||
.AddCommandsFromThisAssembly()
|
||||
.UseTypeActivator(serviceProvider.GetRequiredService)
|
||||
.UseTypeActivator(commandTypes =>
|
||||
{
|
||||
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<LibraryProvider>();
|
||||
|
||||
// Register all commands as transient services
|
||||
foreach (var commandType in commandTypes)
|
||||
services.AddTransient(commandType);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
})
|
||||
.Build()
|
||||
.RunAsync();
|
||||
@@ -156,14 +156,23 @@ public class TypeActivationSpecs : SpecsBase
|
||||
"""
|
||||
);
|
||||
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddSingleton(commandType, Activator.CreateInstance(commandType, "Hello world")!)
|
||||
.BuildServiceProvider();
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommand(commandType)
|
||||
.UseConsole(FakeConsole)
|
||||
.UseTypeActivator(serviceProvider)
|
||||
.UseTypeActivator(commandTypes =>
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
foreach (var serviceType in commandTypes)
|
||||
{
|
||||
services.AddSingleton(
|
||||
serviceType,
|
||||
Activator.CreateInstance(serviceType, "Hello world")!
|
||||
);
|
||||
}
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
})
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
|
||||
@@ -33,7 +33,6 @@ public partial class CliApplicationBuilder
|
||||
public CliApplicationBuilder AddCommand(Type commandType)
|
||||
{
|
||||
_commandTypes.Add(commandType);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -112,7 +111,7 @@ public partial class CliApplicationBuilder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets application title, which is shown in the help text.
|
||||
/// Sets the application title, which is shown in the help text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, application title is inferred from the assembly name.
|
||||
@@ -124,11 +123,10 @@ public partial class CliApplicationBuilder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets application executable name, which is shown in the help text.
|
||||
/// Sets the application executable name, which is shown in the help text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, application executable name is inferred from the assembly file name.
|
||||
/// The file name is also prefixed with `dotnet` if it's a DLL file.
|
||||
/// </remarks>
|
||||
public CliApplicationBuilder SetExecutableName(string executableName)
|
||||
{
|
||||
@@ -137,8 +135,7 @@ public partial class CliApplicationBuilder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets application version, which is shown in the help text or
|
||||
/// when the user specifies the version option.
|
||||
/// Sets the application version, which is shown in the help text or when the user specifies the version option.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, application version is inferred from the assembly version.
|
||||
@@ -150,7 +147,7 @@ public partial class CliApplicationBuilder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets application description, which is shown in the help text.
|
||||
/// Sets the application description, which is shown in the help text.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder SetDescription(string? description)
|
||||
{
|
||||
@@ -177,10 +174,10 @@ public partial class CliApplicationBuilder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the application to use the specified function for activating types.
|
||||
/// Configures the application to use the specified delegate for activating types.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseTypeActivator(Func<Type, object> typeActivator) =>
|
||||
UseTypeActivator(new DelegateTypeActivator(typeActivator));
|
||||
public CliApplicationBuilder UseTypeActivator(Func<Type, object> createInstance) =>
|
||||
UseTypeActivator(new DelegateTypeActivator(createInstance));
|
||||
|
||||
/// <summary>
|
||||
/// Configures the application to use the specified service provider for activating types.
|
||||
@@ -188,6 +185,14 @@ public partial class CliApplicationBuilder
|
||||
public CliApplicationBuilder UseTypeActivator(IServiceProvider serviceProvider) =>
|
||||
UseTypeActivator(serviceProvider.GetService);
|
||||
|
||||
/// <summary>
|
||||
/// Configures the application to use the specified service provider for activating types.
|
||||
/// This method takes a delegate that receives the list of all added command types, so that you can
|
||||
/// easily register them with the service provider.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseTypeActivator(Func<IReadOnlyList<Type>, IServiceProvider> getServiceProvider) =>
|
||||
UseTypeActivator(getServiceProvider(_commandTypes.ToArray()));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a configured instance of <see cref="CliApplication" />.
|
||||
/// </summary>
|
||||
@@ -221,45 +226,56 @@ public partial class CliApplicationBuilder
|
||||
{
|
||||
var entryAssemblyName = EnvironmentEx.EntryAssembly?.GetName().Name;
|
||||
if (string.IsNullOrWhiteSpace(entryAssemblyName))
|
||||
return "App";
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Failed to infer the default application title. " +
|
||||
$"Please specify it explicitly using {nameof(SetTitle)}()."
|
||||
);
|
||||
}
|
||||
|
||||
return entryAssemblyName;
|
||||
}
|
||||
|
||||
private static string GetDefaultExecutableName()
|
||||
{
|
||||
var entryAssemblyLocation = EnvironmentEx.EntryAssembly?.Location;
|
||||
if (string.IsNullOrWhiteSpace(entryAssemblyLocation))
|
||||
return "app";
|
||||
var entryAssemblyFilePath = EnvironmentEx.EntryAssembly?.Location;
|
||||
var processFilePath = EnvironmentEx.ProcessPath;
|
||||
|
||||
// If the application was launched via matching EXE apphost, use that as the executable name
|
||||
var isLaunchedViaAppHost = string.Equals(
|
||||
EnvironmentEx.ProcessPath,
|
||||
Path.ChangeExtension(entryAssemblyLocation, ".exe"),
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
);
|
||||
if (string.IsNullOrWhiteSpace(entryAssemblyFilePath) || string.IsNullOrWhiteSpace(processFilePath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Failed to infer the default application executable name. " +
|
||||
$"Please specify it explicitly using {nameof(SetExecutableName)}()."
|
||||
);
|
||||
}
|
||||
|
||||
if (isLaunchedViaAppHost)
|
||||
return Path.GetFileNameWithoutExtension(entryAssemblyLocation);
|
||||
// If the process path matches the entry assembly path, it's a legacy .NET Framework app
|
||||
// or a self-contained .NET Core app.
|
||||
if (PathEx.AreEqual(entryAssemblyFilePath, processFilePath))
|
||||
return Path.GetFileNameWithoutExtension(entryAssemblyFilePath);
|
||||
|
||||
// Otherwise, use the entry assembly as the executable name.
|
||||
// Prefix it with `dotnet` if it's a DLL file.
|
||||
var isDll = string.Equals(
|
||||
Path.GetExtension(entryAssemblyLocation),
|
||||
".dll",
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
);
|
||||
// If the process path has the same name and parent directory as the entry assembly path,
|
||||
// but different extension, it's a framework-dependent .NET Core app launched through the apphost.
|
||||
if (PathEx.AreEqual(Path.ChangeExtension(entryAssemblyFilePath, ".exe"), processFilePath) ||
|
||||
PathEx.AreEqual(Path.ChangeExtension(entryAssemblyFilePath, ""), processFilePath))
|
||||
{
|
||||
return Path.GetFileNameWithoutExtension(entryAssemblyFilePath);
|
||||
}
|
||||
|
||||
return isDll
|
||||
? "dotnet " + Path.GetFileName(entryAssemblyLocation)
|
||||
: Path.GetFileNameWithoutExtension(entryAssemblyLocation);
|
||||
// Otherwise, it's a framework-dependent .NET Core app launched through the .NET CLI
|
||||
return "dotnet " + Path.GetFileName(entryAssemblyFilePath);
|
||||
}
|
||||
|
||||
private static string GetDefaultVersionText()
|
||||
{
|
||||
var entryAssemblyVersion = EnvironmentEx.EntryAssembly?.GetName().Version;
|
||||
if (entryAssemblyVersion is null)
|
||||
return "v1.0";
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Failed to infer the default application version. " +
|
||||
$"Please specify it explicitly using {nameof(SetVersion)}()."
|
||||
);
|
||||
}
|
||||
|
||||
return "v" + entryAssemblyVersion.ToSemanticString();
|
||||
}
|
||||
|
||||
@@ -4,22 +4,22 @@ using CliFx.Exceptions;
|
||||
namespace CliFx.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ITypeActivator" /> that instantiates an object
|
||||
/// by using a predefined function.
|
||||
/// Implementation of <see cref="ITypeActivator" /> that instantiates an object by using a predefined delegate.
|
||||
/// </summary>
|
||||
public class DelegateTypeActivator : ITypeActivator
|
||||
{
|
||||
private readonly Func<Type, object> _func;
|
||||
private readonly Func<Type, object> _createInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="DelegateTypeActivator" />.
|
||||
/// </summary>
|
||||
public DelegateTypeActivator(Func<Type, object> func) => _func = func;
|
||||
public DelegateTypeActivator(Func<Type, object> createInstance) =>
|
||||
_createInstance = createInstance;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object CreateInstance(Type type)
|
||||
{
|
||||
var instance = _func(type);
|
||||
var instance = _createInstance(type);
|
||||
|
||||
if (instance is null)
|
||||
{
|
||||
|
||||
22
CliFx/Utils/PathEx.cs
Normal file
22
CliFx/Utils/PathEx.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CliFx.Utils;
|
||||
|
||||
internal static class PathEx
|
||||
{
|
||||
private static StringComparer EqualityComparer { get; } =
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? StringComparer.OrdinalIgnoreCase
|
||||
: StringComparer.Ordinal;
|
||||
|
||||
public static bool AreEqual(string path1, string path2)
|
||||
{
|
||||
static string Normalize(string path) => Path
|
||||
.GetFullPath(path)
|
||||
.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
|
||||
return EqualityComparer.Equals(Normalize(path1), Normalize(path2));
|
||||
}
|
||||
}
|
||||
30
Readme.md
30
Readme.md
@@ -531,24 +531,24 @@ The following example shows how to configure your application to use [`Microsoft
|
||||
```csharp
|
||||
public static class Program
|
||||
{
|
||||
public static async Task<int> Main()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Register services
|
||||
services.AddSingleton<MyService>();
|
||||
|
||||
// Register commands
|
||||
services.AddTransient<MyCommand>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
return await new CliApplicationBuilder()
|
||||
public static async Task<int> Main() =>
|
||||
await new CliApplicationBuilder()
|
||||
.AddCommandsFromThisAssembly()
|
||||
.UseTypeActivator(serviceProvider)
|
||||
.UseTypeActivator(commandTypes =>
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Register services
|
||||
services.AddSingleton<MyService>();
|
||||
|
||||
// Register commands
|
||||
foreach (var commandType in commandTypes)
|
||||
services.AddTransient(commandType);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
})
|
||||
.Build()
|
||||
.RunAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user