Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,5 @@ 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.


44 changes: 44 additions & 0 deletions src/Hosting.CommandLine/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,49 @@ public static async Task<int> RunCommandLineApplicationAsync(

return state.ExitCode;
}

/// <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>
/// <param name="configure">The delegate to configure the application</param>
/// <returns><see cref="IHostBuilder"/></returns>
public static IHostBuilder UseCommandLineApplication<TApp>(
this IHostBuilder hostBuilder,
string[] args,
Action<CommandLineApplication<TApp>> configure = null)
where TApp : class
{
configure ??= app => { };
var state = new CommandLineState(args);
hostBuilder.Properties[typeof(CommandLineState)] = state;
hostBuilder.ConfigureServices(
(context, services)
=>
{
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)
.AddSingleton<ICommandLineService, CommandLineService<TApp>>();
services
.AddSingleton(configure);
});
Comment on lines +164 to +188
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be largely a duplication of

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);
});
. Did you consider ways to eliminate or reduce the duplication?


return hostBuilder;
}
}
}
44 changes: 44 additions & 0 deletions src/Hosting.CommandLine/HostExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Nate McMaster.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.Extensions.Hosting
{
using System;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils;
using McMaster.Extensions.Hosting.CommandLine.Internal;
using Microsoft.Extensions.DependencyInjection;
Comment on lines +6 to +12
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit-pick: can you move usings to the top of the file instead of nesting inside the namespace? Sorry VS doesn't provide a way to enforce this style consistently :-/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. I think you can set that in .editorconfig

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion! I've added to editorconfig.


/// <summary>
/// Extension methods for <see cref="IHost" /> support.
/// </summary>
public static class HostExtensions
{
/// <summary>
/// Runs an instance of <typeparamref name="TApp" /> 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
{
var exceptionHandler = host.Services.GetService<StoreExceptionHandler>();
var state = host.Services.GetRequiredService<CommandLineState>();

await host.RunAsync(cancellationToken);

if (exceptionHandler?.StoredException != null)
{
ExceptionDispatchInfo.Capture(exceptionHandler.StoredException).Throw();
}

Comment on lines +31 to +40
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to duplicate

using var host = hostBuilder.Build();
await host.RunAsync(cancellationToken);
if (exceptionHandler.StoredException != null)
{
ExceptionDispatchInfo.Capture(exceptionHandler.StoredException).Throw();
}
return state.ExitCode;
. Same question as above. Could we reduce the duplication? Maybe the implementation of the existing Task<int> RunCommandLineApplicationAsync method could be replaced with two chained calls to new API you are proposing in this PR.

return state.ExitCode;
}
}
}
3 changes: 3 additions & 0 deletions src/Hosting.CommandLine/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
#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>!