diff --git a/README.md b/README.md index f9f2e74d..c1007e91 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ The library also includes other utilities for interaction with the console. Thes var args = new [] { "Arg1", "arg with space", "args ' with \" quotes" }; Process.Start("echo", ArgumentEscaper.EscapeAndConcatenate(args)); ``` - - `Prompt` - for getting feedback from users with a default answer. + - `Prompt` - for getting feedback from users with a default answer. A few examples: ```c# // allows y/n responses, will return false by default in this case. @@ -129,5 +129,3 @@ The library also includes other utilities for interaction with the console. Thes ``` And more! See the [documentation](https://natemcmaster.github.io/CommandLineUtils/) for more API, such as `IConsole`, `IReporter`, and others. - - diff --git a/src/Hosting.CommandLine/HostBuilderExtensions.cs b/src/Hosting.CommandLine/HostBuilderExtensions.cs index c8ea8a8e..6bcb4e9c 100644 --- a/src/Hosting.CommandLine/HostBuilderExtensions.cs +++ b/src/Hosting.CommandLine/HostBuilderExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using McMaster.Extensions.CommandLineUtils; @@ -36,7 +35,7 @@ public static async Task RunCommandLineApplicationAsync( CancellationToken cancellationToken = default) where TApp : class { - return await RunCommandLineApplicationAsync(hostBuilder, args, null, cancellationToken); + return await RunCommandLineApplicationAsync(hostBuilder, args, app => { }, cancellationToken); } /// @@ -57,40 +56,19 @@ public static async Task RunCommandLineApplicationAsync( CancellationToken cancellationToken = default) where TApp : class { - configure ??= app => { }; - var exceptionHandler = new StoreExceptionHandler(); var state = new CommandLineState(args); hostBuilder.Properties[typeof(CommandLineState)] = state; hostBuilder.ConfigureServices( (context, services) => { - services - .TryAddSingleton(exceptionHandler); - services - .AddSingleton() - .TryAddSingleton(PhysicalConsole.Singleton); - services - .AddSingleton(provider => - { - state.SetConsole(provider.GetService()); - return state; - }) - .AddSingleton(state) - .AddSingleton>(); - services - .AddSingleton(configure); + services.AddCommonServices(state); + services.AddSingleton>(); + services.AddSingleton(configure); }); using var host = hostBuilder.Build(); - await host.RunAsync(cancellationToken); - - if (exceptionHandler.StoredException != null) - { - ExceptionDispatchInfo.Capture(exceptionHandler.StoredException).Throw(); - } - - return state.ExitCode; + return await host.RunCommandLineApplicationAsync(cancellationToken); } /// @@ -110,41 +88,33 @@ public static async Task RunCommandLineApplicationAsync( Action configure, CancellationToken cancellationToken = default) { - var exceptionHandler = new StoreExceptionHandler(); var state = new CommandLineState(args); hostBuilder.Properties[typeof(CommandLineState)] = state; hostBuilder.ConfigureServices( (context, services) => { - services - .TryAddSingleton(exceptionHandler); - services - .AddSingleton() - .TryAddSingleton(PhysicalConsole.Singleton); - services - .AddSingleton(provider => - { - state.SetConsole(provider.GetService()); - return state; - }) - .AddSingleton(state) - .AddSingleton(); - services - .AddSingleton(configure); + services.AddCommonServices(state); + services.AddSingleton(); + services.AddSingleton(configure); }); using var host = hostBuilder.Build(); + return await host.RunCommandLineApplicationAsync(cancellationToken); + } - await host.RunAsync(cancellationToken); - - if (exceptionHandler.StoredException != null) - { - ExceptionDispatchInfo.Capture(exceptionHandler.StoredException).Throw(); - } + /// + /// Configures an instance of using to provide + /// command line parsing on the given . + /// + /// The type of the command line application implementation + /// This instance + /// The command line arguments + /// + public static IHostBuilder UseCommandLineApplication(this IHostBuilder hostBuilder, string[] args) + where TApp : class + => UseCommandLineApplication(hostBuilder, args, _ => { }); - return state.ExitCode; - } /// /// Configures an instance of using to provide @@ -158,15 +128,14 @@ public static async Task RunCommandLineApplicationAsync( public static IHostBuilder UseCommandLineApplication( this IHostBuilder hostBuilder, string[] args, - Action> configure = null) + Action> configure) where TApp : class { configure ??= app => { }; var state = new CommandLineState(args); hostBuilder.Properties[typeof(CommandLineState)] = state; hostBuilder.ConfigureServices( - (context, services) - => + (context, services) => { services .TryAddSingleton(); @@ -189,5 +158,23 @@ public static IHostBuilder UseCommandLineApplication( return hostBuilder; } + + private static void AddCommonServices(this IServiceCollection services, CommandLineState state) + { + services + .TryAddSingleton(); + services + .TryAddSingleton(provider => provider.GetRequiredService()); + services + .AddSingleton() + .TryAddSingleton(PhysicalConsole.Singleton); + services + .AddSingleton(provider => + { + state.SetConsole(provider.GetService()); + return state; + }) + .AddSingleton(state); + } } } diff --git a/src/Hosting.CommandLine/HostExtensions.cs b/src/Hosting.CommandLine/HostExtensions.cs index 89028887..5e006f31 100644 --- a/src/Hosting.CommandLine/HostExtensions.cs +++ b/src/Hosting.CommandLine/HostExtensions.cs @@ -17,16 +17,12 @@ namespace Microsoft.Extensions.Hosting public static class HostExtensions { /// - /// Runs an instance of using the previously configured in + /// Runs the app using the previously configured in /// . /// - /// The type of the command line application implementation - /// This instance - /// A cancellation token - public static async Task RunCommandLineApplicationAsync( - this IHost host, - CancellationToken cancellationToken = default) - where TApp : class + /// A program abstraction. + /// Propagates notification that operations should be canceled. + public static async Task RunCommandLineApplicationAsync(this IHost host, CancellationToken cancellationToken = default) { var exceptionHandler = host.Services.GetService(); var state = host.Services.GetRequiredService(); diff --git a/src/Hosting.CommandLine/PublicAPI.Unshipped.txt b/src/Hosting.CommandLine/PublicAPI.Unshipped.txt index df256ebe..fdb48fda 100644 --- a/src/Hosting.CommandLine/PublicAPI.Unshipped.txt +++ b/src/Hosting.CommandLine/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ #nullable enable -static Microsoft.Extensions.Hosting.HostBuilderExtensions.UseCommandLineApplication(this Microsoft.Extensions.Hosting.IHostBuilder! hostBuilder, string![]! args, System.Action!>! configure = null) -> Microsoft.Extensions.Hosting.IHostBuilder! Microsoft.Extensions.Hosting.HostExtensions -static Microsoft.Extensions.Hosting.HostExtensions.RunCommandLineApplicationAsync(this Microsoft.Extensions.Hosting.IHost! host, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +static Microsoft.Extensions.Hosting.HostBuilderExtensions.UseCommandLineApplication(this Microsoft.Extensions.Hosting.IHostBuilder! hostBuilder, string![]! args) -> Microsoft.Extensions.Hosting.IHostBuilder! +static Microsoft.Extensions.Hosting.HostBuilderExtensions.UseCommandLineApplication(this Microsoft.Extensions.Hosting.IHostBuilder! hostBuilder, string![]! args, System.Action!>! configure) -> Microsoft.Extensions.Hosting.IHostBuilder! +static Microsoft.Extensions.Hosting.HostExtensions.RunCommandLineApplicationAsync(this Microsoft.Extensions.Hosting.IHost! host, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! diff --git a/test/Hosting.CommandLine.Tests/CustomValueParserTests.cs b/test/Hosting.CommandLine.Tests/CustomValueParserTests.cs index 9ab4a65d..3cbfa408 100644 --- a/test/Hosting.CommandLine.Tests/CustomValueParserTests.cs +++ b/test/Hosting.CommandLine.Tests/CustomValueParserTests.cs @@ -31,8 +31,7 @@ public async Task ItParsesUsingCustomParserFromConfigAction() .ConfigureServices(collection => collection.AddSingleton(new TestConsole(_output))) .RunCommandLineApplicationAsync( new[] { "--custom-type", DemoOptionValue }, - app => app.ValueParsers.AddOrReplace( - new CustomValueParser())); + app => app.ValueParsers.AddOrReplace(new CustomValueParser())); Assert.Equal(0, exitCode); } diff --git a/test/Hosting.CommandLine.Tests/HostBuilderExtensionsAttributeAPITests.cs b/test/Hosting.CommandLine.Tests/HostBuilderExtensionsAttributeAPITests.cs index 59648375..32c18d3c 100644 --- a/test/Hosting.CommandLine.Tests/HostBuilderExtensionsAttributeAPITests.cs +++ b/test/Hosting.CommandLine.Tests/HostBuilderExtensionsAttributeAPITests.cs @@ -74,6 +74,29 @@ public async Task TestConfigureCommandLineApplication() Assert.NotNull(commandLineApp); } + [Fact] + public async Task TestUseCommandLineApplication() + { + CommandLineApplication commandLineApp = default; + var hostBuilder = new HostBuilder(); + hostBuilder.UseCommandLineApplication(new string[0], app => commandLineApp = app); + var host = hostBuilder.Build(); + await host.RunCommandLineApplicationAsync(); + Assert.NotNull(commandLineApp); + } + + [Fact] + public async Task UseCommandLineApplicationReThrowsExceptions() + { + var ex = await Assert.ThrowsAsync( + () => new HostBuilder() + .ConfigureServices(collection => collection.AddSingleton(new TestConsole(_output))) + .UseCommandLineApplication(new string[0]) + .Build() + .RunCommandLineApplicationAsync()); + Assert.Equal("A test", ex.Message); + } + [Fact] public async Task ItThrowsOnUnknownSubCommand() { diff --git a/test/Hosting.CommandLine.Tests/HostBuilderExtensionsBuilderAPITests.cs b/test/Hosting.CommandLine.Tests/HostBuilderExtensionsBuilderAPITests.cs index c3aa1d2d..21347133 100644 --- a/test/Hosting.CommandLine.Tests/HostBuilderExtensionsBuilderAPITests.cs +++ b/test/Hosting.CommandLine.Tests/HostBuilderExtensionsBuilderAPITests.cs @@ -108,7 +108,7 @@ public async Task TestUsingServiceProvider() public async Task TestCommandLineContextFromNonDIContexts() { CommandLineContext configureServicesContext = null; - CommandLineContext confgureAppContext = null; + CommandLineContext configureAppContext = null; await new HostBuilder() .ConfigureServices((context, collection) => { @@ -117,14 +117,14 @@ public async Task TestCommandLineContextFromNonDIContexts() }) .ConfigureAppConfiguration((context, builder) => { - confgureAppContext = context.GetCommandLineContext(); + configureAppContext = context.GetCommandLineContext(); }) .RunCommandLineApplicationAsync(new string[0], app => app.OnExecute(() => { })); Assert.NotNull(configureServicesContext); - Assert.NotNull(confgureAppContext); + Assert.NotNull(configureAppContext); } } }