diff --git a/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Commands/OtherCommand.cs b/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Commands/OtherCommand.cs
new file mode 100644
index 0000000..e8bd7a7
--- /dev/null
+++ b/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Commands/OtherCommand.cs
@@ -0,0 +1,45 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Community.Extensions.Spectre.Cli.Hosting.Sample.Commands;
+
+///
+/// Another command, just to show that multiple commands can be added
+///
+public class OtherCommand : AsyncCommand
+{
+ private readonly IAnsiConsole _console;
+
+ ///
+ /// Creates a OtherCommand with access to the console and logging
+ ///
+ ///
+ ///
+ public OtherCommand(IAnsiConsole console, ILogger log)
+ {
+ _console = console;
+ }
+
+ /// Executes the command.
+ /// The command context.
+ /// The command options.
+ /// An integer indicating whether or not the command executed successfully.
+ public override async Task ExecuteAsync(CommandContext context, Options options)
+ {
+ _console.MarkupLineInterpolated($"[springgreen2_1] Other {options.Stuff}![/]");
+
+ return 0;
+ }
+
+ [Description("OtherOptions")]
+ public class Options : CommandSettings
+ {
+ [Description("Other Stuff")]
+ [CommandArgument(0, "")]
+ public string? Stuff { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Program.cs b/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Program.cs
index ee4752e..c1e819e 100644
--- a/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Program.cs
+++ b/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Program.cs
@@ -1,33 +1,34 @@
using System.Diagnostics;
+using Community.Extensions.Spectre.Cli.Hosting;
+using Community.Extensions.Spectre.Cli.Hosting.Sample.Commands;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Spectre.Console;
using Spectre.Console.Cli;
-using Community.Extensions.Spectre.Cli.Hosting;
-using Community.Extensions.Spectre.Cli.Hosting.Sample.Commands;
var builder = Host.CreateApplicationBuilder(args);
-builder.Logging.AddSimpleConsole();
-// Yes this is duplicated, this is the one we'll use and that
-// can receive services from the outer host service provider.
-builder.Services.AddCommand();
+// Add a command and optionally configure it.
+builder.Services.AddCommand("hello", cmd =>
+{
+ cmd.WithDescription("A command that says hello");
+});
+// Add another command
+builder.Services.AddCommand("other");
+
+//
+// The standard call save for the commands will be pre-added & configured
+//
builder.UseSpectreConsole(config =>
{
+ // All commands above are passed to config.AddCommand() by this point
#if DEBUG
config.PropagateExceptions();
config.ValidateExamples();
#endif
config.SetApplicationName("hello");
- config.SetExceptionHandler(BasicExceptionHandler.WriteException);
-
- // This configures the command with the internal service provider.
- // Unfortunately, it comes after the external service provider & host have
- // already been built. In future configuration should be extracted to a builder
- // that can be configured prior to service provider creation allowing the two
- // AddCommand calls to be combined.
- config.AddCommand("hello");
+ config.UseBasicExceptionHandler();
});
var app = builder.Build();
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Properties/launchSettings.json b/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Properties/launchSettings.json
index a8897fe..a6140d1 100644
--- a/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Properties/launchSettings.json
+++ b/src/Community.Extensions.Spectre.Cli.Hosting.Sample/Properties/launchSettings.json
@@ -1,8 +1,23 @@
{
"profiles": {
- "Community.Extensions.Spectre.Cli.Hosting.Sample": {
+ "Sample": {
"commandName": "Project",
- "commandLineArgs": "You -p Sadie"
+ //"commandLineArgs": "You -p Sadie"
+ //"commandLineArgs": "other stuff"
+ "commandLineArgs": "--help"
+ },
+ "Interactive": {
+ "commandName": "Executable",
+ "executablePath": "pwsh.exe",
+ "commandLineArgs": "-NoExit -c \"Set-Alias -Name hello -Value \"$(TargetDir)$(AssemblyName).exe\"",
+ "workingDirectory": "$(ProjectDir)"
}
+ /*, For reference only
+ "WT": {
+ "commandName": "Executable",
+ "executablePath": "wt.exe",
+ "commandLineArgs": "pwsh.exe -NoExit -c \"Set-Alias -Name hello -Value \"$(TargetDir)$(AssemblyName).exe\"",
+ "workingDirectory": "$(ProjectDir)"
+ }*/
}
}
\ No newline at end of file
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting/BasicExceptionHandler.cs b/src/Community.Extensions.Spectre.Cli.Hosting/BasicExceptionHandler.cs
index 4a368f9..f08d06a 100644
--- a/src/Community.Extensions.Spectre.Cli.Hosting/BasicExceptionHandler.cs
+++ b/src/Community.Extensions.Spectre.Cli.Hosting/BasicExceptionHandler.cs
@@ -8,6 +8,15 @@ namespace Community.Extensions.Spectre.Cli.Hosting;
///
public static class BasicExceptionHandler
{
+ ///
+ /// Sets the exception handler to write the exception to the AnsiConsole.
+ ///
+ ///
+ ///
+ public static IConfigurator UseBasicExceptionHandler(this IConfigurator configurator)
+ {
+ return configurator.SetExceptionHandler(WriteException);
+ }
///
/// Writes the exception to the AnsiConsole.
///
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistration.TCommand.cs b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistration.TCommand.cs
new file mode 100644
index 0000000..73e7ec8
--- /dev/null
+++ b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistration.TCommand.cs
@@ -0,0 +1,24 @@
+using Spectre.Console.Cli;
+
+namespace Community.Extensions.Spectre.Cli.Hosting.Internal;
+
+///
+/// A typed registration class for commands with their types and name
+///
+///
+///
+public record CommandRegistration(string Name, Action? CommandConfigurator = null)
+ : CommandRegistration(typeof(TCommand), Name) where TCommand : class, ICommand
+{
+ ///
+ ///
+ ///
+ public override void Configure(IConfigurator configuration)
+ {
+ // Add the command to Spectre's configuration
+ var cmdConfig = configuration.AddCommand(Name);
+
+ // Optionally configure the command
+ CommandConfigurator?.Invoke(cmdConfig);
+ }
+}
\ No newline at end of file
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistration.cs b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistration.cs
new file mode 100644
index 0000000..46f07ed
--- /dev/null
+++ b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistration.cs
@@ -0,0 +1,16 @@
+using Spectre.Console.Cli;
+
+namespace Community.Extensions.Spectre.Cli.Hosting.Internal;
+
+///
+/// A base registration class for commands with their types and name
+///
+///
+///
+public abstract record CommandRegistration(Type CommandType, string Name)
+{
+ ///
+ ///
+ ///
+ public abstract void Configure(IConfigurator configuration);
+}
\ No newline at end of file
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistrationExtensions.cs b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistrationExtensions.cs
new file mode 100644
index 0000000..2d9be50
--- /dev/null
+++ b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CommandRegistrationExtensions.cs
@@ -0,0 +1,63 @@
+using System.Text;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Hosting.Internal;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace Community.Extensions.Spectre.Cli.Hosting.Internal;
+
+///
+/// Extends with SpectreConsole commands.
+///
+public static class CommandRegistrationExtensions
+{
+
+ ///
+ /// Returns registered commands
+ ///
+ ///
+ ///
+ public static IEnumerable GetRegisteredCommands(this IServiceProvider serviceProvider) =>
+ serviceProvider.GetServices();
+
+ ///
+ /// Registers a command with it's primary type, name and optional configuration action
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IServiceCollection RegisterCommand(this IServiceCollection services, string name,
+ Action? commandConfigurator = null)
+ where TCommand : class, ICommand
+ {
+ return services.AddTransient>(c =>
+ new CommandRegistration(name, commandConfigurator));
+ }
+
+ ///
+ /// Adds registered commands to the provided app and allows further customization of the app and commands
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static ICommandApp ConfigureAppAndRegisteredCommands(this ICommandApp app, IServiceProvider provider, Action? configureCommandApp = null)
+ {
+ app.Configure(config =>
+ {
+ // Add/Configure registered commands
+ foreach (var cmd in provider.GetRegisteredCommands())
+ {
+ cmd.Configure(config);
+ }
+
+ // Optionally allow caller to configure the command app
+ configureCommandApp?.Invoke(config);
+ });
+
+ return app;
+ }
+}
\ No newline at end of file
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting/CustomTypeRegistrar.cs b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CustomTypeRegistrar.cs
similarity index 98%
rename from src/Community.Extensions.Spectre.Cli.Hosting/CustomTypeRegistrar.cs
rename to src/Community.Extensions.Spectre.Cli.Hosting/Internal/CustomTypeRegistrar.cs
index 5160d53..cf761f3 100644
--- a/src/Community.Extensions.Spectre.Cli.Hosting/CustomTypeRegistrar.cs
+++ b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CustomTypeRegistrar.cs
@@ -3,7 +3,7 @@
using Spectre.Console.Cli;
-namespace Community.Extensions.Spectre.Cli.Hosting;
+namespace Community.Extensions.Spectre.Cli.Hosting.Internal;
internal sealed class CustomTypeRegistrar : ITypeRegistrar
{
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting/CustomTypeResolver.cs b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CustomTypeResolver.cs
similarity index 96%
rename from src/Community.Extensions.Spectre.Cli.Hosting/CustomTypeResolver.cs
rename to src/Community.Extensions.Spectre.Cli.Hosting/Internal/CustomTypeResolver.cs
index b880a29..0f62673 100644
--- a/src/Community.Extensions.Spectre.Cli.Hosting/CustomTypeResolver.cs
+++ b/src/Community.Extensions.Spectre.Cli.Hosting/Internal/CustomTypeResolver.cs
@@ -2,7 +2,7 @@
using Microsoft.Extensions.Logging;
using Spectre.Console.Cli;
-namespace Community.Extensions.Spectre.Cli.Hosting;
+namespace Community.Extensions.Spectre.Cli.Hosting.Internal;
internal sealed class CustomTypeResolver : ITypeResolver, IDisposable
{
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting/SpectreConsoleHostBuilderExtensions.cs b/src/Community.Extensions.Spectre.Cli.Hosting/SpectreConsoleHostBuilderExtensions.cs
index ef05e2d..b660d72 100644
--- a/src/Community.Extensions.Spectre.Cli.Hosting/SpectreConsoleHostBuilderExtensions.cs
+++ b/src/Community.Extensions.Spectre.Cli.Hosting/SpectreConsoleHostBuilderExtensions.cs
@@ -1,4 +1,5 @@
using System.Text;
+using Community.Extensions.Spectre.Cli.Hosting.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.Internal;
@@ -13,31 +14,32 @@ namespace Community.Extensions.Spectre.Cli.Hosting;
public static class SpectreConsoleHostBuilderExtensions
{
///
- /// Adds a command and it's options to the service collection
+ /// Adds a command and it's options to the service collection. Also registers the command
+ /// to be added & configured during the UseSpectreConsole call.
///
///
- ///
///
+ ///
+ /// The configuration action applied to the command
///
- public static IServiceCollection AddCommand(this IServiceCollection services)
- where TCommand : class, ICommand
- where TOptions : CommandSettings
+ public static IServiceCollection AddCommand(this IServiceCollection services, string name,
+ Action? commandConfigurator = null)
+ where TCommand : class, ICommand
{
- // Could use ConfigurationHelper.GetSettingsType(typeof(TCommand)) but I want options flexible
services.AddSingleton();
- services.AddTransient();
+ services.RegisterCommand(name, commandConfigurator);
return services;
}
///
- /// Adds the internal services to the host builder.
+ /// Adds the internal services to the host builder.
///
///
///
private static HostApplicationBuilder AddInternalServices(HostApplicationBuilder builder)
{
- System.Console.OutputEncoding = Encoding.Default;
+ Console.OutputEncoding = Encoding.Default;
builder.Services.AddHostedService();
builder.Services.AddSingleton(x => AnsiConsole.Console);
@@ -60,14 +62,8 @@ public static HostApplicationBuilder UseSpectreConsole(this HostApplicationBuild
builder.Services.AddSingleton(x =>
{
- var command = new CommandApp(new CustomTypeRegistrar(builder.Services, x));
-
- if (configureCommandApp != null)
- {
- command.Configure(configureCommandApp);
- }
-
- return command;
+ var app = new CommandApp(new CustomTypeRegistrar(builder.Services, x));
+ return app.ConfigureAppAndRegisteredCommands(x, configureCommandApp);
});
return AddInternalServices(builder);
@@ -89,14 +85,9 @@ public static HostApplicationBuilder UseSpectreConsole(this Hos
builder.Services.AddSingleton(x =>
{
- var command = new CommandApp(new CustomTypeRegistrar(builder.Services, x));
-
- if (configureCommandApp != null)
- {
- command.Configure(configureCommandApp);
- }
-
- return command;
+ // Create the command app
+ var app = new CommandApp(new CustomTypeRegistrar(builder.Services, x));
+ return app.ConfigureAppAndRegisteredCommands(x, configureCommandApp);
});
return AddInternalServices(builder);
diff --git a/src/Community.Extensions.Spectre.Cli.Hosting/SpectreConsoleWorker.cs b/src/Community.Extensions.Spectre.Cli.Hosting/SpectreConsoleWorker.cs
index 02994d5..f02c3a7 100644
--- a/src/Community.Extensions.Spectre.Cli.Hosting/SpectreConsoleWorker.cs
+++ b/src/Community.Extensions.Spectre.Cli.Hosting/SpectreConsoleWorker.cs
@@ -4,6 +4,9 @@
namespace Community.Extensions.Spectre.Cli.Hosting;
+///
+/// A background service that runs the Spectre Console App
+///
public class SpectreConsoleWorker : BackgroundService
{
private readonly ICommandApp _commandApp;
@@ -14,6 +17,12 @@ public class SpectreConsoleWorker : BackgroundService
private int _exitCode;
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public SpectreConsoleWorker(ILogger logger, ICommandApp commandApp,
IHostApplicationLifetime hostLifetime)
{