Skip to content

Commit 643f9c3

Browse files
cbcrousehellfirehdnatemcmaster
authored
refactor: code deduplication and added/fixed tests for new hosting APIs (#415)
Co-authored-by: Doug Wilson <[email protected]> Co-authored-by: Nate McMaster <[email protected]>
1 parent 47d1194 commit 643f9c3

File tree

7 files changed

+76
-72
lines changed

7 files changed

+76
-72
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ The library also includes other utilities for interaction with the console. Thes
112112
var args = new [] { "Arg1", "arg with space", "args ' with \" quotes" };
113113
Process.Start("echo", ArgumentEscaper.EscapeAndConcatenate(args));
114114
```
115-
- `Prompt` - for getting feedback from users with a default answer.
115+
- `Prompt` - for getting feedback from users with a default answer.
116116
A few examples:
117117
```c#
118118
// 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
129129
```
130130

131131
And more! See the [documentation](https://natemcmaster.github.io/CommandLineUtils/) for more API, such as `IConsole`, `IReporter`, and others.
132-
133-

src/Hosting.CommandLine/HostBuilderExtensions.cs

Lines changed: 41 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Runtime.ExceptionServices;
65
using System.Threading;
76
using System.Threading.Tasks;
87
using McMaster.Extensions.CommandLineUtils;
@@ -36,7 +35,7 @@ public static async Task<int> RunCommandLineApplicationAsync<TApp>(
3635
CancellationToken cancellationToken = default)
3736
where TApp : class
3837
{
39-
return await RunCommandLineApplicationAsync<TApp>(hostBuilder, args, null, cancellationToken);
38+
return await RunCommandLineApplicationAsync<TApp>(hostBuilder, args, app => { }, cancellationToken);
4039
}
4140

4241
/// <summary>
@@ -57,40 +56,19 @@ public static async Task<int> RunCommandLineApplicationAsync<TApp>(
5756
CancellationToken cancellationToken = default)
5857
where TApp : class
5958
{
60-
configure ??= app => { };
61-
var exceptionHandler = new StoreExceptionHandler();
6259
var state = new CommandLineState(args);
6360
hostBuilder.Properties[typeof(CommandLineState)] = state;
6461
hostBuilder.ConfigureServices(
6562
(context, services)
6663
=>
6764
{
68-
services
69-
.TryAddSingleton<IUnhandledExceptionHandler>(exceptionHandler);
70-
services
71-
.AddSingleton<IHostLifetime, CommandLineLifetime>()
72-
.TryAddSingleton(PhysicalConsole.Singleton);
73-
services
74-
.AddSingleton(provider =>
75-
{
76-
state.SetConsole(provider.GetService<IConsole>());
77-
return state;
78-
})
79-
.AddSingleton<CommandLineContext>(state)
80-
.AddSingleton<ICommandLineService, CommandLineService<TApp>>();
81-
services
82-
.AddSingleton(configure);
65+
services.AddCommonServices(state);
66+
services.AddSingleton<ICommandLineService, CommandLineService<TApp>>();
67+
services.AddSingleton(configure);
8368
});
8469

8570
using var host = hostBuilder.Build();
86-
await host.RunAsync(cancellationToken);
87-
88-
if (exceptionHandler.StoredException != null)
89-
{
90-
ExceptionDispatchInfo.Capture(exceptionHandler.StoredException).Throw();
91-
}
92-
93-
return state.ExitCode;
71+
return await host.RunCommandLineApplicationAsync(cancellationToken);
9472
}
9573

9674
/// <summary>
@@ -110,41 +88,33 @@ public static async Task<int> RunCommandLineApplicationAsync(
11088
Action<CommandLineApplication> configure,
11189
CancellationToken cancellationToken = default)
11290
{
113-
var exceptionHandler = new StoreExceptionHandler();
11491
var state = new CommandLineState(args);
11592
hostBuilder.Properties[typeof(CommandLineState)] = state;
11693
hostBuilder.ConfigureServices(
11794
(context, services)
11895
=>
11996
{
120-
services
121-
.TryAddSingleton<IUnhandledExceptionHandler>(exceptionHandler);
122-
services
123-
.AddSingleton<IHostLifetime, CommandLineLifetime>()
124-
.TryAddSingleton(PhysicalConsole.Singleton);
125-
services
126-
.AddSingleton(provider =>
127-
{
128-
state.SetConsole(provider.GetService<IConsole>());
129-
return state;
130-
})
131-
.AddSingleton<CommandLineContext>(state)
132-
.AddSingleton<ICommandLineService, CommandLineService>();
133-
services
134-
.AddSingleton(configure);
97+
services.AddCommonServices(state);
98+
services.AddSingleton<ICommandLineService, CommandLineService>();
99+
services.AddSingleton(configure);
135100
});
136101

137102
using var host = hostBuilder.Build();
103+
return await host.RunCommandLineApplicationAsync(cancellationToken);
104+
}
138105

139-
await host.RunAsync(cancellationToken);
140-
141-
if (exceptionHandler.StoredException != null)
142-
{
143-
ExceptionDispatchInfo.Capture(exceptionHandler.StoredException).Throw();
144-
}
106+
/// <summary>
107+
/// Configures an instance of <typeparamref name="TApp" /> using <see cref="CommandLineApplication" /> to provide
108+
/// command line parsing on the given <paramref name="args" />.
109+
/// </summary>
110+
/// <typeparam name="TApp">The type of the command line application implementation</typeparam>
111+
/// <param name="hostBuilder">This instance</param>
112+
/// <param name="args">The command line arguments</param>
113+
/// <returns><see cref="IHostBuilder"/></returns>
114+
public static IHostBuilder UseCommandLineApplication<TApp>(this IHostBuilder hostBuilder, string[] args)
115+
where TApp : class
116+
=> UseCommandLineApplication<TApp>(hostBuilder, args, _ => { });
145117

146-
return state.ExitCode;
147-
}
148118

149119
/// <summary>
150120
/// Configures an instance of <typeparamref name="TApp" /> using <see cref="CommandLineApplication" /> to provide
@@ -158,15 +128,14 @@ public static async Task<int> RunCommandLineApplicationAsync(
158128
public static IHostBuilder UseCommandLineApplication<TApp>(
159129
this IHostBuilder hostBuilder,
160130
string[] args,
161-
Action<CommandLineApplication<TApp>> configure = null)
131+
Action<CommandLineApplication<TApp>> configure)
162132
where TApp : class
163133
{
164134
configure ??= app => { };
165135
var state = new CommandLineState(args);
166136
hostBuilder.Properties[typeof(CommandLineState)] = state;
167137
hostBuilder.ConfigureServices(
168-
(context, services)
169-
=>
138+
(context, services) =>
170139
{
171140
services
172141
.TryAddSingleton<StoreExceptionHandler>();
@@ -189,5 +158,23 @@ public static IHostBuilder UseCommandLineApplication<TApp>(
189158

190159
return hostBuilder;
191160
}
161+
162+
private static void AddCommonServices(this IServiceCollection services, CommandLineState state)
163+
{
164+
services
165+
.TryAddSingleton<StoreExceptionHandler>();
166+
services
167+
.TryAddSingleton<IUnhandledExceptionHandler>(provider => provider.GetRequiredService<StoreExceptionHandler>());
168+
services
169+
.AddSingleton<IHostLifetime, CommandLineLifetime>()
170+
.TryAddSingleton(PhysicalConsole.Singleton);
171+
services
172+
.AddSingleton(provider =>
173+
{
174+
state.SetConsole(provider.GetService<IConsole>());
175+
return state;
176+
})
177+
.AddSingleton<CommandLineContext>(state);
178+
}
192179
}
193180
}

src/Hosting.CommandLine/HostExtensions.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,12 @@ namespace Microsoft.Extensions.Hosting
1717
public static class HostExtensions
1818
{
1919
/// <summary>
20-
/// Runs an instance of <typeparamref name="TApp" /> using the <see cref="CommandLineApplication" /> previously configured in
20+
/// Runs the app using the <see cref="CommandLineApplication" /> previously configured in
2121
/// <see cref="HostBuilderExtensions.UseCommandLineApplication{TApp}(IHostBuilder, string[], Action{CommandLineApplication{TApp}})"/>.
2222
/// </summary>
23-
/// <typeparam name="TApp">The type of the command line application implementation</typeparam>
24-
/// <param name="host">This instance</param>
25-
/// <param name="cancellationToken">A cancellation token</param>
26-
public static async Task<int> RunCommandLineApplicationAsync<TApp>(
27-
this IHost host,
28-
CancellationToken cancellationToken = default)
29-
where TApp : class
23+
/// <param name="host">A program abstraction.</param>
24+
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
25+
public static async Task<int> RunCommandLineApplicationAsync(this IHost host, CancellationToken cancellationToken = default)
3026
{
3127
var exceptionHandler = host.Services.GetService<StoreExceptionHandler>();
3228
var state = host.Services.GetRequiredService<CommandLineState>();
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#nullable enable
2-
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!
32
Microsoft.Extensions.Hosting.HostExtensions
4-
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>!
3+
static Microsoft.Extensions.Hosting.HostBuilderExtensions.UseCommandLineApplication<TApp>(this Microsoft.Extensions.Hosting.IHostBuilder! hostBuilder, string![]! args) -> Microsoft.Extensions.Hosting.IHostBuilder!
4+
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!
5+
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>!

test/Hosting.CommandLine.Tests/CustomValueParserTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ public async Task ItParsesUsingCustomParserFromConfigAction()
3131
.ConfigureServices(collection => collection.AddSingleton<IConsole>(new TestConsole(_output)))
3232
.RunCommandLineApplicationAsync<CustomOptionTypeCommand>(
3333
new[] { "--custom-type", DemoOptionValue },
34-
app => app.ValueParsers.AddOrReplace(
35-
new CustomValueParser()));
34+
app => app.ValueParsers.AddOrReplace(new CustomValueParser()));
3635
Assert.Equal(0, exitCode);
3736
}
3837

test/Hosting.CommandLine.Tests/HostBuilderExtensionsAttributeAPITests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,29 @@ public async Task TestConfigureCommandLineApplication()
7474
Assert.NotNull(commandLineApp);
7575
}
7676

77+
[Fact]
78+
public async Task TestUseCommandLineApplication()
79+
{
80+
CommandLineApplication<Return42Command> commandLineApp = default;
81+
var hostBuilder = new HostBuilder();
82+
hostBuilder.UseCommandLineApplication<Return42Command>(new string[0], app => commandLineApp = app);
83+
var host = hostBuilder.Build();
84+
await host.RunCommandLineApplicationAsync();
85+
Assert.NotNull(commandLineApp);
86+
}
87+
88+
[Fact]
89+
public async Task UseCommandLineApplicationReThrowsExceptions()
90+
{
91+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
92+
() => new HostBuilder()
93+
.ConfigureServices(collection => collection.AddSingleton<IConsole>(new TestConsole(_output)))
94+
.UseCommandLineApplication<ThrowsExceptionCommand>(new string[0])
95+
.Build()
96+
.RunCommandLineApplicationAsync());
97+
Assert.Equal("A test", ex.Message);
98+
}
99+
77100
[Fact]
78101
public async Task ItThrowsOnUnknownSubCommand()
79102
{

test/Hosting.CommandLine.Tests/HostBuilderExtensionsBuilderAPITests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public async Task TestUsingServiceProvider()
108108
public async Task TestCommandLineContextFromNonDIContexts()
109109
{
110110
CommandLineContext configureServicesContext = null;
111-
CommandLineContext confgureAppContext = null;
111+
CommandLineContext configureAppContext = null;
112112
await new HostBuilder()
113113
.ConfigureServices((context, collection) =>
114114
{
@@ -117,14 +117,14 @@ public async Task TestCommandLineContextFromNonDIContexts()
117117
})
118118
.ConfigureAppConfiguration((context, builder) =>
119119
{
120-
confgureAppContext = context.GetCommandLineContext();
120+
configureAppContext = context.GetCommandLineContext();
121121
})
122122
.RunCommandLineApplicationAsync(new string[0], app => app.OnExecute(() =>
123123
{
124124
}));
125125

126126
Assert.NotNull(configureServicesContext);
127-
Assert.NotNull(confgureAppContext);
127+
Assert.NotNull(configureAppContext);
128128
}
129129
}
130130
}

0 commit comments

Comments
 (0)