Skip to content

Commit

Permalink
refactor: code deduplication and added/fixed tests for new hosting AP…
Browse files Browse the repository at this point in the history
…Is (#415)

Co-authored-by: Doug Wilson <d@dkw.io>
Co-authored-by: Nate McMaster <nate.mcmaster@gmail.com>
  • Loading branch information
3 people authored Jan 9, 2021
1 parent 47d1194 commit 643f9c3
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 72 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.

95 changes: 41 additions & 54 deletions src/Hosting.CommandLine/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -36,7 +35,7 @@ public static async Task<int> RunCommandLineApplicationAsync<TApp>(
CancellationToken cancellationToken = default)
where TApp : class
{
return await RunCommandLineApplicationAsync<TApp>(hostBuilder, args, null, cancellationToken);
return await RunCommandLineApplicationAsync<TApp>(hostBuilder, args, app => { }, cancellationToken);
}

/// <summary>
Expand All @@ -57,40 +56,19 @@ public static async Task<int> RunCommandLineApplicationAsync<TApp>(
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<IUnhandledExceptionHandler>(exceptionHandler);
services
.AddSingleton<IHostLifetime, CommandLineLifetime>()
.TryAddSingleton(PhysicalConsole.Singleton);
services
.AddSingleton(provider =>
{
state.SetConsole(provider.GetService<IConsole>());
return state;
})
.AddSingleton<CommandLineContext>(state)
.AddSingleton<ICommandLineService, CommandLineService<TApp>>();
services
.AddSingleton(configure);
services.AddCommonServices(state);
services.AddSingleton<ICommandLineService, CommandLineService<TApp>>();
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);
}

/// <summary>
Expand All @@ -110,41 +88,33 @@ public static async Task<int> RunCommandLineApplicationAsync(
Action<CommandLineApplication> configure,
CancellationToken cancellationToken = default)
{
var exceptionHandler = new StoreExceptionHandler();
var state = new CommandLineState(args);
hostBuilder.Properties[typeof(CommandLineState)] = state;
hostBuilder.ConfigureServices(
(context, services)
=>
{
services
.TryAddSingleton<IUnhandledExceptionHandler>(exceptionHandler);
services
.AddSingleton<IHostLifetime, CommandLineLifetime>()
.TryAddSingleton(PhysicalConsole.Singleton);
services
.AddSingleton(provider =>
{
state.SetConsole(provider.GetService<IConsole>());
return state;
})
.AddSingleton<CommandLineContext>(state)
.AddSingleton<ICommandLineService, CommandLineService>();
services
.AddSingleton(configure);
services.AddCommonServices(state);
services.AddSingleton<ICommandLineService, CommandLineService>();
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();
}
/// <summary>
/// Configures an instance of <typeparamref name="TApp" /> using <see cref="CommandLineApplication" /> to provide
/// command line parsing on the given <paramref name="args" />.
/// </summary>
/// <typeparam name="TApp">The type of the command line application implementation</typeparam>
/// <param name="hostBuilder">This instance</param>
/// <param name="args">The command line arguments</param>
/// <returns><see cref="IHostBuilder"/></returns>
public static IHostBuilder UseCommandLineApplication<TApp>(this IHostBuilder hostBuilder, string[] args)
where TApp : class
=> UseCommandLineApplication<TApp>(hostBuilder, args, _ => { });

return state.ExitCode;
}

/// <summary>
/// Configures an instance of <typeparamref name="TApp" /> using <see cref="CommandLineApplication" /> to provide
Expand All @@ -158,15 +128,14 @@ public static async Task<int> RunCommandLineApplicationAsync(
public static IHostBuilder UseCommandLineApplication<TApp>(
this IHostBuilder hostBuilder,
string[] args,
Action<CommandLineApplication<TApp>> configure = null)
Action<CommandLineApplication<TApp>> configure)
where TApp : class
{
configure ??= app => { };
var state = new CommandLineState(args);
hostBuilder.Properties[typeof(CommandLineState)] = state;
hostBuilder.ConfigureServices(
(context, services)
=>
(context, services) =>
{
services
.TryAddSingleton<StoreExceptionHandler>();
Expand All @@ -189,5 +158,23 @@ public static IHostBuilder UseCommandLineApplication<TApp>(

return hostBuilder;
}

private static void AddCommonServices(this IServiceCollection services, CommandLineState state)
{
services
.TryAddSingleton<StoreExceptionHandler>();
services
.TryAddSingleton<IUnhandledExceptionHandler>(provider => provider.GetRequiredService<StoreExceptionHandler>());
services
.AddSingleton<IHostLifetime, CommandLineLifetime>()
.TryAddSingleton(PhysicalConsole.Singleton);
services
.AddSingleton(provider =>
{
state.SetConsole(provider.GetService<IConsole>());
return state;
})
.AddSingleton<CommandLineContext>(state);
}
}
}
12 changes: 4 additions & 8 deletions src/Hosting.CommandLine/HostExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,12 @@ namespace Microsoft.Extensions.Hosting
public static class HostExtensions
{
/// <summary>
/// Runs an instance of <typeparamref name="TApp" /> using the <see cref="CommandLineApplication" /> previously configured in
/// Runs the app using the <see cref="CommandLineApplication" /> previously configured in
/// <see cref="HostBuilderExtensions.UseCommandLineApplication{TApp}(IHostBuilder, string[], Action{CommandLineApplication{TApp}})"/>.
/// </summary>
/// <typeparam name="TApp">The type of the command line application implementation</typeparam>
/// <param name="host">This instance</param>
/// <param name="cancellationToken">A cancellation token</param>
public static async Task<int> RunCommandLineApplicationAsync<TApp>(
this IHost host,
CancellationToken cancellationToken = default)
where TApp : class
/// <param name="host">A program abstraction.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
public static async Task<int> RunCommandLineApplicationAsync(this IHost host, CancellationToken cancellationToken = default)
{
var exceptionHandler = host.Services.GetService<StoreExceptionHandler>();
var state = host.Services.GetRequiredService<CommandLineState>();
Expand Down
5 changes: 3 additions & 2 deletions src/Hosting.CommandLine/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
static Microsoft.Extensions.Hosting.HostBuilderExtensions.UseCommandLineApplication<TApp>(this Microsoft.Extensions.Hosting.IHostBuilder! hostBuilder, string![]! args, System.Action<McMaster.Extensions.CommandLineUtils.CommandLineApplication<TApp!>!>! configure = null) -> Microsoft.Extensions.Hosting.IHostBuilder!
Microsoft.Extensions.Hosting.HostExtensions
static Microsoft.Extensions.Hosting.HostExtensions.RunCommandLineApplicationAsync<TApp>(this Microsoft.Extensions.Hosting.IHost! host, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<int>!
static Microsoft.Extensions.Hosting.HostBuilderExtensions.UseCommandLineApplication<TApp>(this Microsoft.Extensions.Hosting.IHostBuilder! hostBuilder, string![]! args) -> Microsoft.Extensions.Hosting.IHostBuilder!
static Microsoft.Extensions.Hosting.HostBuilderExtensions.UseCommandLineApplication<TApp>(this Microsoft.Extensions.Hosting.IHostBuilder! hostBuilder, string![]! args, System.Action<McMaster.Extensions.CommandLineUtils.CommandLineApplication<TApp!>!>! 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<int>!
3 changes: 1 addition & 2 deletions test/Hosting.CommandLine.Tests/CustomValueParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ public async Task ItParsesUsingCustomParserFromConfigAction()
.ConfigureServices(collection => collection.AddSingleton<IConsole>(new TestConsole(_output)))
.RunCommandLineApplicationAsync<CustomOptionTypeCommand>(
new[] { "--custom-type", DemoOptionValue },
app => app.ValueParsers.AddOrReplace(
new CustomValueParser()));
app => app.ValueParsers.AddOrReplace(new CustomValueParser()));
Assert.Equal(0, exitCode);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ public async Task TestConfigureCommandLineApplication()
Assert.NotNull(commandLineApp);
}

[Fact]
public async Task TestUseCommandLineApplication()
{
CommandLineApplication<Return42Command> commandLineApp = default;
var hostBuilder = new HostBuilder();
hostBuilder.UseCommandLineApplication<Return42Command>(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<InvalidOperationException>(
() => new HostBuilder()
.ConfigureServices(collection => collection.AddSingleton<IConsole>(new TestConsole(_output)))
.UseCommandLineApplication<ThrowsExceptionCommand>(new string[0])
.Build()
.RunCommandLineApplicationAsync());
Assert.Equal("A test", ex.Message);
}

[Fact]
public async Task ItThrowsOnUnknownSubCommand()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
{
Expand All @@ -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);
}
}
}

0 comments on commit 643f9c3

Please sign in to comment.