187 lines
6.8 KiB
C#
187 lines
6.8 KiB
C#
using System;
|
|
using System.CommandLine;
|
|
using System.CommandLine.Builder;
|
|
using System.CommandLine.Hosting;
|
|
using System.CommandLine.Parsing;
|
|
using System.IO;
|
|
|
|
using Autofac;
|
|
using Autofac.Extensions.DependencyInjection;
|
|
|
|
using MfGames.ToolBuilder.Services;
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
using Serilog;
|
|
|
|
namespace MfGames.ToolBuilder
|
|
{
|
|
/// <summary>
|
|
/// A builder pattern for creating a tool box, a nested collection of tools.
|
|
/// This wraps much of the hosting and command-line parsing infrastructure
|
|
/// with some opinionated decisions and reduces the amount of boilerplate
|
|
/// needed to create tools.
|
|
/// </summary>
|
|
public class ToolBoxBuilder
|
|
{
|
|
private readonly string[] arguments;
|
|
|
|
private readonly ConfigToolGlobal config;
|
|
|
|
private readonly IHostBuilder hostBuilder;
|
|
|
|
private readonly LoggingToolGlobal logging;
|
|
|
|
public ToolBoxBuilder(string configName, string[] arguments)
|
|
{
|
|
// Create our various services.
|
|
this.arguments = arguments;
|
|
this.config = new ConfigToolGlobal(configName);
|
|
this.logging = new LoggingToolGlobal();
|
|
|
|
// Set up logging first so we can report the loading process. This
|
|
// sets up the Serilog.Log.Logger which means we can use that for
|
|
// everything beyond this point.
|
|
this.logging.Configure(arguments);
|
|
|
|
// Start up the basic configuration.
|
|
//
|
|
// Normally, we would use Host.CreateDefaultBuilder(args) to create
|
|
// this, but when run as a stand-alone application in someone's
|
|
// $HOME on Linux, this causes an inotify watcher to be registered
|
|
// on the entire directory tree (which takes a really long time).
|
|
//
|
|
// We also don't need most of the default features.
|
|
var serviceProviderFactory = new AutofacServiceProviderFactory();
|
|
this.hostBuilder = new HostBuilder()
|
|
.UseDefaultServiceProvider(this.ConfigureDefaultServiceProvider)
|
|
.UseConsoleLifetime()
|
|
.ConfigureAppConfiguration(this.ConfigureAppConfiguration)
|
|
.UseSerilog()
|
|
.UseServiceProviderFactory(serviceProviderFactory)
|
|
.ConfigureServices(this.ConfigureServices)
|
|
.ConfigureContainer<ContainerBuilder>(this.ConfigureContainer);
|
|
}
|
|
|
|
public static ToolBoxBuilder Create(
|
|
string configName,
|
|
string[] arguments)
|
|
{
|
|
return new ToolBoxBuilder(configName, arguments);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a tool box, a collection of tools.
|
|
/// </summary>
|
|
/// <returns>The constructed toolbox.</returns>
|
|
public ToolBox Build()
|
|
{
|
|
// There is a Catch-22 with how System.CommandLine is build. It
|
|
// requires the root command to be passed into the constructor
|
|
// for the CommandLineBuilder but we can't create the root command
|
|
// until we build our host.
|
|
//
|
|
// To handle this, we build our first host builder (which doesn't
|
|
// know about the command line parser), use it to get the command
|
|
// and then pass it into the command-line version to create a
|
|
// second `HostBuilder` that we don't use.
|
|
IHost host = this.hostBuilder.Build();
|
|
ILifetimeScope container = host.Services.GetAutofacRoot()!;
|
|
|
|
// Create the root command and attach our globals to that.
|
|
RootCommand rootCommand = container.Resolve<RootCommand>();
|
|
|
|
this.config.Attach(rootCommand);
|
|
this.logging.Attach(rootCommand);
|
|
|
|
// Finish creating the command line builder so we can make the parser.
|
|
CommandLineBuilder? builder = new CommandLineBuilder(rootCommand)
|
|
.UseDefaults()
|
|
.UseHost();
|
|
|
|
// Finish building the parser, wrap it in a tool box, and return it.
|
|
Parser parser = builder.Build();
|
|
var toolBox = new ToolBox(this.arguments, parser);
|
|
|
|
return toolBox;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides additional configuration for Autofac containers.
|
|
/// </summary>
|
|
/// <param name="configure">The configuration method.</param>
|
|
/// <returns>The builder to chain methods.</returns>
|
|
public ToolBoxBuilder ConfigureContainer(
|
|
Action<ContainerBuilder> configure)
|
|
{
|
|
this.hostBuilder.ConfigureContainer(configure);
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides additional configuration for services.
|
|
/// </summary>
|
|
/// <param name="configure">The configuration method.</param>
|
|
/// <returns>The builder to chain methods.</returns>
|
|
public ToolBoxBuilder ConfigureServices(
|
|
Action<IServiceCollection> configure)
|
|
{
|
|
this.hostBuilder.ConfigureServices(configure);
|
|
return this;
|
|
}
|
|
|
|
private void ConfigureAppConfiguration(
|
|
HostBuilderContext context,
|
|
IConfigurationBuilder builder)
|
|
{
|
|
builder.SetBasePath(Directory.GetCurrentDirectory());
|
|
this.config.Configure(builder, this.arguments);
|
|
}
|
|
|
|
private void ConfigureContainer(
|
|
HostBuilderContext context,
|
|
ContainerBuilder builder)
|
|
{
|
|
// We want to get logging up and running as soon as possible. We
|
|
// also hook up the logging to the process exit in an attempt to
|
|
// make sure the logger is properly flushed before exiting.
|
|
builder.RegisterInstance(Log.Logger).As<ILogger>().SingleInstance();
|
|
|
|
AppDomain.CurrentDomain.ProcessExit +=
|
|
(_, _) => Log.CloseAndFlush();
|
|
|
|
// Register the global services as singletons.
|
|
builder
|
|
.RegisterInstance(this.config)
|
|
.AsSelf()
|
|
.SingleInstance();
|
|
builder
|
|
.RegisterInstance(this.logging)
|
|
.AsSelf()
|
|
.SingleInstance();
|
|
|
|
// Register the components required to make the CLI work.
|
|
builder.RegisterModule<ToolBuilderModule>();
|
|
}
|
|
|
|
private void ConfigureDefaultServiceProvider(
|
|
HostBuilderContext context,
|
|
ServiceProviderOptions options)
|
|
{
|
|
bool isDevelopment = context.HostingEnvironment.IsDevelopment();
|
|
|
|
options.ValidateScopes = isDevelopment;
|
|
options.ValidateOnBuild = isDevelopment;
|
|
}
|
|
|
|
private void ConfigureServices(
|
|
HostBuilderContext context,
|
|
IServiceCollection services)
|
|
{
|
|
services.AddAutofac();
|
|
}
|
|
}
|
|
}
|