diff --git a/.idea/.idea.Ploch.CommandLine/.idea/.name b/.idea/.idea.Ploch.CommandLine/.idea/.name
deleted file mode 100644
index 22bd15d..0000000
--- a/.idea/.idea.Ploch.CommandLine/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-Ploch.CommandLine
\ No newline at end of file
diff --git a/.idea/.idea.Ploch.CommandLine/.idea/git_toolbox_blame.xml b/.idea/.idea.Ploch.CommandLine/.idea/git_toolbox_blame.xml
deleted file mode 100644
index 7dc1249..0000000
--- a/.idea/.idea.Ploch.CommandLine/.idea/git_toolbox_blame.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.Ploch.CommandLine/.idea/indexLayout.xml b/.idea/.idea.Ploch.CommandLine/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163..0000000
--- a/.idea/.idea.Ploch.CommandLine/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/qodana.yaml b/qodana.yaml
index ce2a708..dd3433f 100644
--- a/qodana.yaml
+++ b/qodana.yaml
@@ -1,6 +1,6 @@
version: "1.0"
-linter: jetbrains/qodana-cdnet:2024.1
+linter: jetbrains/qodana-cdnet:latest
profile:
name: qodana.recommended
include:
- - name: CheckDependencyLicenses
\ No newline at end of file
+ - name: CheckDependencyLicenses
diff --git a/samples/Directory.Packages.props b/samples/Directory.Packages.props
new file mode 100644
index 0000000..55086e4
--- /dev/null
+++ b/samples/Directory.Packages.props
@@ -0,0 +1,5 @@
+
+
+ false
+
+
\ No newline at end of file
diff --git a/samples/HelloWorldConsoleApp/HelloWorldConsoleApp.csproj b/samples/HelloWorldConsoleApp/HelloWorldConsoleApp.csproj
index 0780d59..7f9123a 100644
--- a/samples/HelloWorldConsoleApp/HelloWorldConsoleApp.csproj
+++ b/samples/HelloWorldConsoleApp/HelloWorldConsoleApp.csproj
@@ -2,7 +2,7 @@
Exe
- net7.0
+ net9.0
enable
enable
Linux
diff --git a/samples/HelloWorldConsoleApp/HellowWorldCommand.cs b/samples/HelloWorldConsoleApp/HellowWorldCommand.cs
new file mode 100644
index 0000000..ae76530
--- /dev/null
+++ b/samples/HelloWorldConsoleApp/HellowWorldCommand.cs
@@ -0,0 +1,37 @@
+using System.ComponentModel.DataAnnotations;
+using McMaster.Extensions.CommandLineUtils;
+using Ploch.Common.CommandLine;
+#pragma warning disable Ex0100
+
+namespace HelloWorldConsoleApp;
+
+[Command(Name = "HelloWorld", Description = "Prints Hello World")]
+#pragma warning disable ClassDocumentationHeader
+public class HellowWorldCommand : IAsyncCommand
+#pragma warning restore ClassDocumentationHeader
+#pragma warning disable MethodDocumentationHeader
+#pragma warning disable PropertyDocumentationHeader
+{
+ [Option(Description = "Person First Name", ShortName = "f")]
+ [Required]
+
+ public required string FirstName { get; set; }
+
+
+ [Option(Description = "Person Last Name", ShortName = "l")]
+ [Required]
+ public required string LastName { get; set; }
+
+ [Option(Description = "Person Age", ShortName = "a")]
+ [Required]
+ public required int Age { get; set; }
+
+
+ public Task OnExecuteAsync(CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"{FirstName} {LastName} ({Age}) - Hello World!");
+
+ return Task.CompletedTask;
+ }
+}
+#pragma warning restore PropertyDocumentationHeader
\ No newline at end of file
diff --git a/samples/HelloWorldConsoleApp/Program.cs b/samples/HelloWorldConsoleApp/Program.cs
index 47c8c91..734aa46 100644
--- a/samples/HelloWorldConsoleApp/Program.cs
+++ b/samples/HelloWorldConsoleApp/Program.cs
@@ -1,31 +1,5 @@
-// See https://aka.ms/new-console-template for more information
-
-using System.ComponentModel.DataAnnotations;
-using McMaster.Extensions.CommandLineUtils;
+using HelloWorldConsoleApp;
using Ploch.Common.CommandLine;
-var app = AppBuilder.CreateDefault().Build();
-app.Command();
-
-[Command(Name = "HelloWorld", Description = "Prints Hello World")]
-public class HellowWorldCommand : IAsyncCommand
-{
- [Option(Description = "Person First Name", ShortName = "f")]
- [Required]
- public required string FirstName { get; set; }
-
- [Option(Description = "Person Last Name", ShortName = "l")]
- [Required]
- public required string LastName { get; set; }
-
- [Option(Description = "Person Age", ShortName = "a")]
- [Required]
- public required int Age { get; set; }
-
- public Task OnExecuteAsync(CancellationToken cancellationToken = default)
- {
- Console.WriteLine($"{FirstName} {LastName} ({Age}) - Hello World!");
-
- return Task.CompletedTask;
- }
-}
\ No newline at end of file
+var app = AppBuilder.CreateDefault("Hello World App", "A sample app").Build();
+app.Command();
\ No newline at end of file
diff --git a/samples/HostingSample/Dockerfile b/samples/HostingSample/Dockerfile
new file mode 100644
index 0000000..a7c5778
--- /dev/null
+++ b/samples/HostingSample/Dockerfile
@@ -0,0 +1,21 @@
+FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
+USER $APP_UID
+WORKDIR /app
+
+FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["HostingSample/HostingSample.csproj", "HostingSample/"]
+RUN dotnet restore "HostingSample/HostingSample.csproj"
+COPY . .
+WORKDIR "/src/HostingSample"
+RUN dotnet build "HostingSample.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "HostingSample.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "HostingSample.dll"]
diff --git a/samples/HostingSample/HelloRootCommand.cs b/samples/HostingSample/HelloRootCommand.cs
new file mode 100644
index 0000000..3970065
--- /dev/null
+++ b/samples/HostingSample/HelloRootCommand.cs
@@ -0,0 +1,14 @@
+using Ploch.Common.CommandLine;
+using McMaster.Extensions.CommandLineUtils;
+using System.ComponentModel.DataAnnotations;
+
+namespace HostingSample;
+public class HelloRootCommand(CommandLineApplication app) : HelpOnlyCommand(app)
+{
+ [Option(Inherited = true)]
+ public bool Verbose { get; set; }
+
+ [Option(Inherited = true)]
+ [Required]
+ public string HelloText { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/samples/HostingSample/HostingSample.csproj b/samples/HostingSample/HostingSample.csproj
new file mode 100644
index 0000000..f36f915
--- /dev/null
+++ b/samples/HostingSample/HostingSample.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net9.0
+ enable
+ enable
+ dotnet-HostingSample-ba67250b-2688-481f-ae6e-990048d3df9a
+ Linux
+
+
+
+
+
+
+
+
+
+ .dockerignore
+
+
+
+
+
+
+
diff --git a/samples/HostingSample/Program.cs b/samples/HostingSample/Program.cs
new file mode 100644
index 0000000..dabbad0
--- /dev/null
+++ b/samples/HostingSample/Program.cs
@@ -0,0 +1,23 @@
+using Autofac.Core;
+using Autofac.Core.Registration;
+using HostingSample;
+
+var builder = Host.CreateApplicationBuilder(args);
+builder.Services.AddHostedService();
+//builder.ConfigureContainer(new AutofacServiceProviderFactory(), collection => collection.RegisterModule);
+
+var host = builder.Build();
+host.Run();
+
+public class AutofacBuilder : IModule
+{
+ public AutofacBuilder()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Configure(IComponentRegistryBuilder componentRegistry)
+ {
+ // componentRegistry.AddRegistrationSource(new Se);
+ }
+}
\ No newline at end of file
diff --git a/samples/HostingSample/Properties/launchSettings.json b/samples/HostingSample/Properties/launchSettings.json
new file mode 100644
index 0000000..f436e37
--- /dev/null
+++ b/samples/HostingSample/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "HostingSample": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "DOTNET_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/samples/HostingSample/Worker.cs b/samples/HostingSample/Worker.cs
new file mode 100644
index 0000000..7ee7c66
--- /dev/null
+++ b/samples/HostingSample/Worker.cs
@@ -0,0 +1,24 @@
+namespace HostingSample;
+
+public class Worker : BackgroundService
+{
+ private readonly ILogger _logger;
+
+ public Worker(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ if (_logger.IsEnabled(LogLevel.Information))
+ {
+ _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
+ }
+
+ await Task.Delay(1000, stoppingToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/HostingSample/appsettings.Development.json b/samples/HostingSample/appsettings.Development.json
new file mode 100644
index 0000000..b2dcdb6
--- /dev/null
+++ b/samples/HostingSample/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/samples/HostingSample/appsettings.json b/samples/HostingSample/appsettings.json
new file mode 100644
index 0000000..b2dcdb6
--- /dev/null
+++ b/samples/HostingSample/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/src/CommandLine.Autofac/Ploch.Common.CommandLine.Autofac.csproj b/src/CommandLine.Autofac/Ploch.Common.CommandLine.Autofac.csproj
index 1618548..be9c3d4 100644
--- a/src/CommandLine.Autofac/Ploch.Common.CommandLine.Autofac.csproj
+++ b/src/CommandLine.Autofac/Ploch.Common.CommandLine.Autofac.csproj
@@ -1,18 +1,22 @@
-
+
- net8.0
+ net9.0
enable
enable
Nullable
+
+ False
+
+
-
-
+
+
all
- runtime; build; native; contentfiles; analyzers; buildtransitive
+ runtime; build; native; contentfiles; analyzers
all
@@ -20,12 +24,12 @@
all
- runtime; build; native; contentfiles; analyzers; buildtransitive
+ runtime; build; native; contentfiles; analyzers
-
+
diff --git a/src/CommandLine.Hosting/IUnhandledExceptionHandler.cs b/src/CommandLine.Hosting/IUnhandledExceptionHandler.cs
new file mode 100644
index 0000000..583951c
--- /dev/null
+++ b/src/CommandLine.Hosting/IUnhandledExceptionHandler.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Nate McMaster.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using McMaster.Extensions.CommandLineUtils;
+using Ploch.CommandLine.Hosting.Internal;
+
+namespace Ploch.CommandLine.Hosting;
+
+///
+/// Used by to handle exceptions that are emitted from the
+/// e.g. during parsing or execution
+///
+public interface IUnhandledExceptionHandler
+{
+ ///
+ /// Handle otherwise uncaught exception. You are free to log, rethrow, … the exception
+ ///
+ /// An otherwise uncaught exception
+ void HandleException(Exception e);
+}
\ No newline at end of file
diff --git a/src/CommandLine.Hosting/Internal/CommandLineLifetime.cs b/src/CommandLine.Hosting/Internal/CommandLineLifetime.cs
new file mode 100644
index 0000000..481e03a
--- /dev/null
+++ b/src/CommandLine.Hosting/Internal/CommandLineLifetime.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Nate McMaster.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.ExceptionServices;
+using McMaster.Extensions.CommandLineUtils;
+using Microsoft.Extensions.Hosting;
+
+namespace Ploch.CommandLine.Hosting.Internal;
+
+///
+/// Waits from completion of the and
+/// initiates shutdown.
+///
+internal class CommandLineLifetime : IHostLifetime, IDisposable
+{
+ private readonly IHostApplicationLifetime _applicationLifetime;
+ private readonly ICommandLineService _cliService;
+ private readonly IConsole _console;
+ private readonly IUnhandledExceptionHandler? _unhandledExceptionHandler;
+
+ ///
+ /// Creates a new instance.
+ ///
+ public CommandLineLifetime(IHostApplicationLifetime applicationLifetime,
+ ICommandLineService cliService,
+ IConsole console,
+ IUnhandledExceptionHandler? unhandledExceptionHandler = null)
+ {
+ _applicationLifetime = applicationLifetime;
+ _cliService = cliService;
+ _console = console;
+ _unhandledExceptionHandler = unhandledExceptionHandler;
+ }
+
+ public void Dispose()
+ { }
+
+ ///
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Registers an ApplicationStarted hook that runs the
+ /// . This ensures the container and all
+ /// hosted services are started before the
+ /// is run. After the
+ /// ICliService completes, the ExitCode is
+ /// recorded and the application is stopped.
+ ///
+ /// Used to indicate when stop should no longer be graceful.
+ ///
+ ///
+ public Task WaitForStartAsync(CancellationToken cancellationToken)
+ {
+ _applicationLifetime.ApplicationStarted.Register(async () =>
+ {
+ try
+ {
+ ExitCode = await _cliService.RunAsync(cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception e)
+ {
+ if (_unhandledExceptionHandler != null)
+ {
+ _unhandledExceptionHandler.HandleException(e);
+ }
+ else
+ {
+ ExceptionDispatchInfo.Capture(e).Throw();
+ }
+ }
+ finally
+ {
+ _applicationLifetime.StopApplication();
+ }
+ });
+
+ // Capture CTRL+C and prevent it from immediately force killing the app.
+ _console.CancelKeyPress += (_, e) =>
+ {
+ e.Cancel = true;
+ _applicationLifetime.StopApplication();
+ };
+
+ return Task.CompletedTask;
+ }
+
+ /// The exit code returned by the command line application
+ public int ExitCode { get; private set; }
+}
\ No newline at end of file
diff --git a/src/CommandLine.Hosting/Internal/CommandLineService.cs b/src/CommandLine.Hosting/Internal/CommandLineService.cs
new file mode 100644
index 0000000..b364463
--- /dev/null
+++ b/src/CommandLine.Hosting/Internal/CommandLineService.cs
@@ -0,0 +1,115 @@
+// Copyright (c) Nate McMaster.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using McMaster.Extensions.CommandLineUtils;
+using McMaster.Extensions.CommandLineUtils.Conventions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Ploch.CommandLine.Hosting.Internal;
+
+///
+/// A service to be run as part of the when using builder API.
+///
+internal class CommandLineService : IDisposable, ICommandLineService
+{
+ private readonly CommandLineApplication _application;
+ private readonly ILogger? _logger;
+ private readonly CommandLineState _state;
+
+ ///
+ /// Creates a new instance.
+ ///
+ /// A logger
+ /// The command line state
+ /// The DI service provider
+ /// The delegate to configure the app
+ public CommandLineService(CommandLineState state,
+ IServiceProvider serviceProvider,
+ Action configure,
+ ILogger? logger = null)
+ {
+ _logger = logger;
+ _state = state;
+
+ logger?.LogDebug("Constructing CommandLineApplication with args [{args}]", string.Join(",", state.Arguments));
+ _application = new CommandLineApplication(state.Console, state.WorkingDirectory);
+
+ _application.Conventions
+ .UseDefaultConventions()
+ .UseConstructorInjection(serviceProvider);
+ foreach (var convention in serviceProvider.GetServices())
+ {
+ _application.Conventions.AddConvention(convention);
+ }
+
+ configure(_application);
+ }
+
+ ///
+ public async Task RunAsync(CancellationToken cancellationToken)
+ {
+ _logger?.LogDebug("Running");
+ _state.ExitCode = await _application.ExecuteAsync(_state.Arguments, cancellationToken);
+ return _state.ExitCode;
+ }
+
+ public void Dispose()
+ {
+ _application.Dispose();
+ }
+}
+
+///
+/// A service to be run as part of the when using attribute API.
+///
+internal class CommandLineService : IDisposable, ICommandLineService
+ where T : class
+{
+ private readonly CommandLineApplication _application;
+ private readonly ILogger _logger;
+ private readonly CommandLineState _state;
+
+ ///
+ /// Creates a new instance.
+ ///
+ /// A logger
+ /// The command line state
+ /// The DI service provider
+ /// The delegate to configure the app
+ public CommandLineService(ILogger> logger,
+ CommandLineState state,
+ IServiceProvider serviceProvider,
+ Action> configure)
+ {
+ _logger = logger;
+ _state = state;
+
+ logger.LogDebug("Constructing CommandLineApplication<{type}> with args [{args}]",
+ typeof(T).FullName, string.Join(",", state.Arguments));
+ _application = new CommandLineApplication(state.Console, state.WorkingDirectory);
+ _application.Conventions
+ .UseDefaultConventions()
+ .UseConstructorInjection(serviceProvider);
+
+ foreach (var convention in serviceProvider.GetServices())
+ {
+ _application.Conventions.AddConvention(convention);
+ }
+
+ configure(_application);
+ }
+
+ ///
+ public async Task RunAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogDebug("Running");
+ _state.ExitCode = await _application.ExecuteAsync(_state.Arguments, cancellationToken);
+ return _state.ExitCode;
+ }
+
+ public void Dispose()
+ {
+ _application.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/CommandLine.Hosting/Internal/CommandLineState.cs b/src/CommandLine.Hosting/Internal/CommandLineState.cs
new file mode 100644
index 0000000..290ca8d
--- /dev/null
+++ b/src/CommandLine.Hosting/Internal/CommandLineState.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Nate McMaster.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using McMaster.Extensions.CommandLineUtils;
+using McMaster.Extensions.CommandLineUtils.Abstractions;
+
+namespace Ploch.CommandLine.Hosting.Internal;
+
+///
+/// A DI container for storing command line arguments.
+///
+internal class CommandLineState : CommandLineContext
+{
+ public CommandLineState(IEnumerable args)
+ {
+ Arguments = args.ToArray();
+ }
+
+ public int ExitCode { get; set; }
+
+ internal void SetConsole(IConsole console)
+ {
+ Console = console;
+ }
+}
\ No newline at end of file
diff --git a/src/CommandLine.Hosting/Internal/ICommandLineService.cs b/src/CommandLine.Hosting/Internal/ICommandLineService.cs
new file mode 100644
index 0000000..b2840f9
--- /dev/null
+++ b/src/CommandLine.Hosting/Internal/ICommandLineService.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Nate McMaster.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Ploch.CommandLine.Hosting.Internal;
+
+///
+/// A service to be run as part of the .
+///
+internal interface ICommandLineService
+{
+ ///
+ /// Runs the application asynchronously and returns the exit code.
+ ///
+ /// Used to indicate when stop should no longer be graceful.
+ /// The exit code
+ Task RunAsync(CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/src/CommandLine.Hosting/Internal/StoreExceptionHandler.cs b/src/CommandLine.Hosting/Internal/StoreExceptionHandler.cs
new file mode 100644
index 0000000..2feafbd
--- /dev/null
+++ b/src/CommandLine.Hosting/Internal/StoreExceptionHandler.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Nate McMaster.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Ploch.CommandLine.Hosting.Internal;
+
+///
+/// Implementation of that stores an unhandled exception so it can later be
+/// rethrown by .
+///
+internal class StoreExceptionHandler : IUnhandledExceptionHandler
+{
+ ///
+ /// The captured exception, if any
+ ///
+ public Exception? StoredException { get; private set; }
+
+ ///
+ /// This will store the first unhandled exception and throw an if called a
+ /// second time.
+ ///
+ /// The unhandled exception to store
+ ///
+ /// If called a second time an containing
+ /// both exceptions is raised
+ ///
+ public void HandleException(Exception e)
+ {
+ if (StoredException != null)
+ {
+ throw new AggregateException("Second exception received!", StoredException, e);
+ }
+
+ StoredException = e;
+ }
+}
\ No newline at end of file
diff --git a/src/CommandLine.Hosting/Ploch.CommandLine.Hosting.csproj b/src/CommandLine.Hosting/Ploch.CommandLine.Hosting.csproj
new file mode 100644
index 0000000..40ead38
--- /dev/null
+++ b/src/CommandLine.Hosting/Ploch.CommandLine.Hosting.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+ False
+
+
+
+
+
+
+
+
+
diff --git a/src/CommandLine.Hosting/ServiceCollectionRegistrations.cs b/src/CommandLine.Hosting/ServiceCollectionRegistrations.cs
new file mode 100644
index 0000000..1e20688
--- /dev/null
+++ b/src/CommandLine.Hosting/ServiceCollectionRegistrations.cs
@@ -0,0 +1,61 @@
+using McMaster.Extensions.CommandLineUtils;
+using McMaster.Extensions.CommandLineUtils.Abstractions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Hosting;
+using Ploch.CommandLine.Hosting.Internal;
+
+namespace Ploch.CommandLine.Hosting;
+
+public static class ServiceCollectionRegistrations
+{
+ /*public static IHostBuilder UseCommandLineApplication(
+ this IHostBuilder hostBuilder,
+ string[] args,
+ Action> configure)
+ where TApp : class
+ {
+ configure ??= _ => { };
+ var state = new CommandLineState(args);
+ hostBuilder.Properties[typeof(CommandLineState)] = state;
+ hostBuilder.ConfigureServices((context, services) =>
+ services
+ .AddCommonServices(state)
+ .AddSingleton>()
+ .AddSingleton(configure));
+
+ return hostBuilder;
+ }*
+ */
+ public static IServiceCollection AddCommandLineAppServices(IServiceCollection services,
+ IEnumerable args,
+ Action> configure)
+ where TApp : class
+ {
+ // ReSharper disable PossibleMultipleEnumeration
+ var state = new CommandLineState(args);
+
+ return services.AddCommonServices(state)
+ .AddSingleton>()
+ .AddSingleton(configure);
+
+ // ReSharper restore PossibleMultipleEnumeration
+ }
+
+ private static IServiceCollection AddCommonServices(this IServiceCollection services, CommandLineState state)
+ {
+ services.TryAddSingleton();
+ services.TryAddSingleton(provider => provider.GetRequiredService());
+ services.TryAddSingleton(PhysicalConsole.Singleton);
+ services
+ .AddSingleton()
+ .AddSingleton(provider =>
+ {
+ state.SetConsole(provider.GetRequiredService());
+ return state;
+ })
+ .AddSingleton(state);
+
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/src/CommandLine.Serilog/LoggingSetup.cs b/src/CommandLine.Serilog/LoggingSetup.cs
index bb2d6c0..8869a05 100644
--- a/src/CommandLine.Serilog/LoggingSetup.cs
+++ b/src/CommandLine.Serilog/LoggingSetup.cs
@@ -28,26 +28,26 @@ public static AppBuilder UseSerilog(this AppBuilder appBuilder, string? logName
private static void ConfigureServices(IServiceCollection serviceCollection, string? logName = null, string? logPath = null)
{
var loggerConfiguration = new LoggerConfiguration().Enrich.FromLogContext()
- .Enrich.WithThreadId()
- .Enrich.WithThreadName()
- .Enrich.FromLogContext()
- .WriteTo.File(BuildFullLogPath(logName, logPath),
- rollOnFileSizeLimit: true,
- fileSizeLimitBytes: 2 * 1024 * 1024,
- // outputTemplate: template,
- retainedFileCountLimit: 10,
- formatProvider: CultureInfo.CurrentCulture)
- .WriteTo.Logger(l => l.Filter.ByIncludingOnly(logEvent =>
- logEvent.Level is LogEventLevel.Error
- or LogEventLevel.Warning
- or LogEventLevel.Fatal))
- .WriteTo.File(BuildFullLogPath(logName, logPath, "errors"),
- rollOnFileSizeLimit: true,
- fileSizeLimitBytes: 2 * 1024 * 1024,
- // outputTemplate: template,
- retainedFileCountLimit: 10,
- formatProvider: CultureInfo.CurrentCulture)
- .WriteTo.Console(formatProvider: CultureInfo.CurrentCulture);
+ .Enrich.WithThreadId()
+ .Enrich.WithThreadName()
+ .Enrich.FromLogContext()
+ .WriteTo.File(BuildFullLogPath(logName, logPath),
+ rollOnFileSizeLimit: true,
+ fileSizeLimitBytes: 2 * 1024 * 1024,
+ // outputTemplate: template,
+ retainedFileCountLimit: 10,
+ formatProvider: CultureInfo.CurrentCulture)
+ .WriteTo.Logger(l => l.Filter.ByIncludingOnly(logEvent =>
+ logEvent.Level is LogEventLevel.Error
+ or LogEventLevel.Warning
+ or LogEventLevel.Fatal))
+ .WriteTo.File(BuildFullLogPath(logName, logPath, "errors"),
+ rollOnFileSizeLimit: true,
+ fileSizeLimitBytes: 2 * 1024 * 1024,
+ // outputTemplate: template,
+ retainedFileCountLimit: 10,
+ formatProvider: CultureInfo.CurrentCulture)
+ .WriteTo.Console(formatProvider: CultureInfo.CurrentCulture).MinimumLevel.Error();
serviceCollection.AddLogging(builder => builder.AddSerilog(loggerConfiguration.CreateLogger()));
}
diff --git a/src/CommandLine.Serilog/Ploch.Common.CommandLine.Serilog.csproj b/src/CommandLine.Serilog/Ploch.Common.CommandLine.Serilog.csproj
index 6875f99..4627918 100644
--- a/src/CommandLine.Serilog/Ploch.Common.CommandLine.Serilog.csproj
+++ b/src/CommandLine.Serilog/Ploch.Common.CommandLine.Serilog.csproj
@@ -1,35 +1,27 @@
-
+
- net8.0
+ net9.0
enable
enable
Nullable
+
+ False
+
+
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+
+
+
-
+
diff --git a/src/CommandLine/AppBuilder.cs b/src/CommandLine/AppBuilder.cs
index e42367d..026246f 100644
--- a/src/CommandLine/AppBuilder.cs
+++ b/src/CommandLine/AppBuilder.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-using Microsoft.Extensions.Configuration;
using McMaster.Extensions.CommandLineUtils;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Ploch.Common.CommandLine;
@@ -32,6 +32,26 @@ private AppBuilder(Func? appBuildFunc,
}
}
+ ///
+ /// Creates a default instance of the with optional configuration
+ /// and command-line arguments.
+ ///
+ /// The application name (or title) shown during execution and in the help screens.
+ /// The application description displayed during execution and in the help screens.
+ ///
+ /// An optional action to apply additional configuration settings to the .
+ ///
+ ///
+ /// An array of command-line arguments to be added to the configuration.
+ ///
+ ///
+ /// Returns an instance of with the default setup and the supplied configuration.
+ ///
+ public static AppBuilder CreateDefault(string name, string description, Action? configurationAction = null, params string[] args)
+ {
+ return CreateDefault(new CommandAppProperties(name, description), configurationAction, args);
+ }
+
///
/// Creates a default instance of the with optional configuration
/// and command-line arguments.
@@ -82,7 +102,7 @@ public AppBuilder Configure(Action configurationAction
///
/// Returns a fully constructed and configured instance of .
///
- public CommandLineApplication Build()
+ public CommandLineApplication BuildConsole()
{
var app = _appBuildFunc();
var serviceCollections = new ServiceCollection();
diff --git a/src/CommandLine/CommandAppProperties.cs b/src/CommandLine/CommandAppProperties.cs
index ac6f965..18bc2af 100644
--- a/src/CommandLine/CommandAppProperties.cs
+++ b/src/CommandLine/CommandAppProperties.cs
@@ -1,3 +1,26 @@
namespace Ploch.Common.CommandLine;
-public record CommandAppProperties(string Name, string Description);
\ No newline at end of file
+public record CommandAppProperties(string Name, string Description);
+
+public class CommandAppPropertiesBuilder
+{
+ private string _description = string.Empty;
+ private string _name = string.Empty;
+
+ public CommandAppPropertiesBuilder WithName(string name)
+ {
+ _name = name;
+ return this;
+ }
+
+ public CommandAppPropertiesBuilder WithDescription(string description)
+ {
+ _description = description;
+ return this;
+ }
+
+ public CommandAppProperties Build()
+ {
+ return new CommandAppProperties(_name, _description);
+ }
+}
\ No newline at end of file
diff --git a/src/CommandLine/CommandLineApplicationConfigurationExtensions.cs b/src/CommandLine/CommandLineApplicationConfigurationExtensions.cs
index 031c599..e4414ba 100644
--- a/src/CommandLine/CommandLineApplicationConfigurationExtensions.cs
+++ b/src/CommandLine/CommandLineApplicationConfigurationExtensions.cs
@@ -4,8 +4,49 @@
namespace Ploch.Common.CommandLine;
+///
+/// Provides extension methods for configuring and enhancing the behavior of
+/// instances.
+///
+///
+/// This class includes methods to add custom validators and configure commands for command-line applications.
+///
public static class CommandLineApplicationConfigurationExtensions
{
+ ///
+ /// Adds a custom validator to the specified
+ /// instance.
+ ///
+ ///
+ /// The type of the model associated with the
+ /// .
+ ///
+ ///
+ /// The instance to which the
+ /// validator will be added.
+ ///
+ ///
+ /// A delegate representing the custom validation logic to be executed before the command is executed.
+ ///
+ ///
+ /// The instance with the added
+ /// validator.
+ ///
+ ///
+ /// This method allows you to add a custom pre-execution validator to a command-line application.
+ /// The validator is executed before the command is executed, ensuring that the application meets
+ /// specific validation criteria.
+ ///
+ ///
+ ///
+ /// var app = new CommandLineApplication<MyModel>();
+ /// app.AddValidator((command, context) =>
+ /// {
+ /// // Custom validation logic
+ /// return ValidationResult.Success;
+ /// });
+ ///
+ ///
public static CommandLineApplication AddValidator(this CommandLineApplication application, PreExecuteCommandValidator validator)
where TModel : class
{
diff --git a/src/CommandLine/ICommand.cs b/src/CommandLine/ICommand.cs
index 8322891..b519539 100644
--- a/src/CommandLine/ICommand.cs
+++ b/src/CommandLine/ICommand.cs
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
-using System.Threading.Tasks;
namespace Ploch.Common.CommandLine;
@@ -11,4 +10,4 @@ public interface ICommand
/// Executed status code integer
[SuppressMessage("ReSharper", "UnusedMemberInSuper.Global", Justification = "Called dynamically by the CommandLineUtils library")]
void OnExecute();
-}
+}
\ No newline at end of file
diff --git a/src/CommandLine/ICommandAppPropertiesConfigurator.cs b/src/CommandLine/ICommandAppPropertiesConfigurator.cs
new file mode 100644
index 0000000..8ef3c6a
--- /dev/null
+++ b/src/CommandLine/ICommandAppPropertiesConfigurator.cs
@@ -0,0 +1,13 @@
+namespace Ploch.Common.CommandLine;
+
+public interface ICommandAppPropertiesConfigurator
+{
+ ICommandAppPropertiesConfigurator WithName(string name);
+
+ ICommandAppPropertiesConfigurator WithDescription(string description);
+}
+
+public interface ICommandAppPropertiesBuilder : ICommandAppPropertiesConfigurator
+{
+ CommandAppProperties Build();
+}
\ No newline at end of file
diff --git a/src/CommandLine/Ploch.Common.CommandLine.csproj b/src/CommandLine/Ploch.Common.CommandLine.csproj
index 9ee6eae..0972614 100644
--- a/src/CommandLine/Ploch.Common.CommandLine.csproj
+++ b/src/CommandLine/Ploch.Common.CommandLine.csproj
@@ -1,25 +1,29 @@
-
+
-
- net8.0
- enable
- Nullable
-
+
+ net9.0
+ enable
+ Nullable
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DemoApp/AdvancedFeaturesSample/AdvancedFeaturesSample.csproj b/src/DemoApp/AdvancedFeaturesSample/AdvancedFeaturesSample.csproj
new file mode 100644
index 0000000..1bdbc04
--- /dev/null
+++ b/src/DemoApp/AdvancedFeaturesSample/AdvancedFeaturesSample.csproj
@@ -0,0 +1,55 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
diff --git a/src/DemoApp/AdvancedFeaturesSample/ISampleService.cs b/src/DemoApp/AdvancedFeaturesSample/ISampleService.cs
new file mode 100644
index 0000000..a8e6bd8
--- /dev/null
+++ b/src/DemoApp/AdvancedFeaturesSample/ISampleService.cs
@@ -0,0 +1,4 @@
+public interface ISampleService
+{
+ void ExecuteSomeAction();
+}
\ No newline at end of file
diff --git a/src/DemoApp/ConsoleApp1/Program.cs b/src/DemoApp/AdvancedFeaturesSample/Program.cs
similarity index 62%
rename from src/DemoApp/ConsoleApp1/Program.cs
rename to src/DemoApp/AdvancedFeaturesSample/Program.cs
index 197fee8..3b504a0 100644
--- a/src/DemoApp/ConsoleApp1/Program.cs
+++ b/src/DemoApp/AdvancedFeaturesSample/Program.cs
@@ -1,4 +1,5 @@
-using ConsoleApp1;
+using BasicConsoleApp;
+using AdvancedFeaturesSample;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ploch.Common.CommandLine;
@@ -9,12 +10,12 @@ await AppBuilder.CreateDefault(new CommandAppProperties("MyTestApp", "My App Des
.UseAutofac()
.Configure(container =>
{
- container.Services.AddSingleton()
- .AddSingleton()
- .AddSingleton();
- container.Application.Command(app =>
+ container.Services.AddSingleton()
+ .AddSingleton()
+ .AddSingleton();
+ container.Application.Command(app =>
{
- app.Command();
+ app.Command();
});
})
.Build()
diff --git a/src/DemoApp/AdvancedFeaturesSample/README.md b/src/DemoApp/AdvancedFeaturesSample/README.md
new file mode 100644
index 0000000..b52bc01
--- /dev/null
+++ b/src/DemoApp/AdvancedFeaturesSample/README.md
@@ -0,0 +1,13 @@
+# Ploch CommandLine Applications Advanced Features Sample
+
+## Overview
+
+This sample demonstrates some of the advanced features of the Ploch CommandLine Applications library.
+
+This includes:
+
+- Using Verbs and Multi-Level Composite Commands
+- Application Configuration
+- Dependency Injection
+- Logging
+
diff --git a/src/DemoApp/AdvancedFeaturesSample/SampleChildCommand.cs b/src/DemoApp/AdvancedFeaturesSample/SampleChildCommand.cs
new file mode 100644
index 0000000..9de0c7f
--- /dev/null
+++ b/src/DemoApp/AdvancedFeaturesSample/SampleChildCommand.cs
@@ -0,0 +1,29 @@
+using BasicConsoleApp;
+using McMaster.Extensions.CommandLineUtils;
+using Ploch.Common.CommandLine;
+
+namespace AdvancedFeaturesSample;
+
+///
+/// This is a sub-command for the command.
+///
+/// The instance.
+/// An interface that is injected here using the dependency injection.
+/// Parent command allowing access to its options.
+[Command(Name = "inner1")]
+public class SampleChildCommand(CommandLineApplication app, ISampleService sampleService, SampleRootCommand parentCommand) : ICommand
+{
+ [Option]
+ public string? SomeOption { get; set; }
+
+ public void OnExecute()
+ {
+ sampleService.ExecuteSomeAction();
+ Console.WriteLine($"{SomeOption}{parentCommand.Verbose}-{parentCommand.Colour}");
+
+ Console.WriteLine("ChildCommand1 executed");
+
+ Console.WriteLine();
+ app.WriteHelpText();
+ }
+}
\ No newline at end of file
diff --git a/src/DemoApp/AdvancedFeaturesSample/SampleHelloWorldService.cs b/src/DemoApp/AdvancedFeaturesSample/SampleHelloWorldService.cs
new file mode 100644
index 0000000..957d390
--- /dev/null
+++ b/src/DemoApp/AdvancedFeaturesSample/SampleHelloWorldService.cs
@@ -0,0 +1,10 @@
+using McMaster.Extensions.CommandLineUtils;
+
+public class SampleHelloWorldService(IConsole console) : ISampleService
+{
+ public void ExecuteSomeAction()
+ {
+ ;
+ console.WriteLine("Hello World from SampleHelloWorldService");
+ }
+}
\ No newline at end of file
diff --git a/src/DemoApp/ConsoleApp1/RootCommand1.cs b/src/DemoApp/AdvancedFeaturesSample/SampleRootCommand.cs
similarity index 69%
rename from src/DemoApp/ConsoleApp1/RootCommand1.cs
rename to src/DemoApp/AdvancedFeaturesSample/SampleRootCommand.cs
index c691666..344d0cb 100644
--- a/src/DemoApp/ConsoleApp1/RootCommand1.cs
+++ b/src/DemoApp/AdvancedFeaturesSample/SampleRootCommand.cs
@@ -1,10 +1,10 @@
using McMaster.Extensions.CommandLineUtils;
using Ploch.Common.CommandLine;
-namespace ConsoleApp1;
+namespace BasicConsoleApp;
[Command(Name = "command1")]
-public class RootCommand1(CommandLineApplication app) : HelpOnlyCommand(app)
+public class SampleRootCommand(CommandLineApplication app) : HelpOnlyCommand(app)
{
[Option(Inherited = true)]
public bool Verbose { get; set; }
diff --git a/src/DemoApp/ConsoleApp1/appsettings.json b/src/DemoApp/AdvancedFeaturesSample/appsettings.json
similarity index 100%
rename from src/DemoApp/ConsoleApp1/appsettings.json
rename to src/DemoApp/AdvancedFeaturesSample/appsettings.json
diff --git a/src/DemoApp/ConsoleApp1/ChildCommand1.cs b/src/DemoApp/ConsoleApp1/ChildCommand1.cs
deleted file mode 100644
index b164ab1..0000000
--- a/src/DemoApp/ConsoleApp1/ChildCommand1.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using McMaster.Extensions.CommandLineUtils;
-using Ploch.Common.CommandLine;
-
-namespace ConsoleApp1;
-
-[Command(Name = "inner1")]
-public class ChildCommand1(CommandLineApplication app, ISomeInterface someInterface, RootCommand1 parentCommand) : ICommand
-{
- [Option]
- public string? SomeOption { get; set; }
-
- public void OnExecute()
- {
- someInterface.SomeMethod();
- Console.WriteLine($"{SomeOption}{parentCommand.Verbose}-{parentCommand.Colour}");
-
- Console.WriteLine("ChildCommand1 executed");
-
- Console.WriteLine();
- app.WriteHelpText();
- }
-}
\ No newline at end of file
diff --git a/src/DemoApp/ConsoleApp1/ConsoleApp1.csproj b/src/DemoApp/ConsoleApp1/ConsoleApp1.csproj
deleted file mode 100644
index cef42f2..0000000
--- a/src/DemoApp/ConsoleApp1/ConsoleApp1.csproj
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
- Exe
- net8.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
-
-
diff --git a/src/DemoApp/ConsoleApp1/ISomeInterface.cs b/src/DemoApp/ConsoleApp1/ISomeInterface.cs
deleted file mode 100644
index e5afe76..0000000
--- a/src/DemoApp/ConsoleApp1/ISomeInterface.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-public interface ISomeInterface
-{
- void SomeMethod();
-}
\ No newline at end of file
diff --git a/src/DemoApp/ConsoleApp1/SomeClass.cs b/src/DemoApp/ConsoleApp1/SomeClass.cs
deleted file mode 100644
index 35b14b1..0000000
--- a/src/DemoApp/ConsoleApp1/SomeClass.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-public class SomeClass : ISomeInterface
-{
- public void SomeMethod()
- {
- Console.WriteLine("SomeMethod");
- }
-}
\ No newline at end of file
diff --git a/tests/CommandLine.IntegrationTests/Ploch.Common.CommandLine.IntegrationTests.csproj b/tests/CommandLine.IntegrationTests/Ploch.Common.CommandLine.IntegrationTests.csproj
index deab123..a1470c0 100644
--- a/tests/CommandLine.IntegrationTests/Ploch.Common.CommandLine.IntegrationTests.csproj
+++ b/tests/CommandLine.IntegrationTests/Ploch.Common.CommandLine.IntegrationTests.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net9.0
enable
enable
@@ -38,10 +38,6 @@
-
-
-
-
diff --git a/tests/CommandLine.IntegrationTests/TestCommand.cs b/tests/CommandLine.IntegrationTests/TestCommand.cs
index 3b2e6d4..cf4db64 100644
--- a/tests/CommandLine.IntegrationTests/TestCommand.cs
+++ b/tests/CommandLine.IntegrationTests/TestCommand.cs
@@ -5,7 +5,8 @@ namespace Ploch.Common.CommandLine.Tests;
[Command(Name = "testCommand")]
public class TestCommand(TestCallback testCallback) : IAsyncCommand
{
- [Option] public string? TestArg { get; set; }
+ [Option]
+ public string? TestArg { get; set; }
public Task OnExecuteAsync(CancellationToken cancellationToken = default)
{
diff --git a/tests/CommandLine.IntegrationTests/TestCommandLineApp.cs b/tests/CommandLine.IntegrationTests/TestCommandLineApp.cs
index af93ab1..9d92faa 100644
--- a/tests/CommandLine.IntegrationTests/TestCommandLineApp.cs
+++ b/tests/CommandLine.IntegrationTests/TestCommandLineApp.cs
@@ -18,7 +18,7 @@ public static int AppMain(string[] args, TestCallback testCallback)
appContainer.Application.Command();
})
- .Build()
+ .BuildConsole()
.Execute(args);
}
}
\ No newline at end of file