This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
mfgames-toolbuilder-cil/src/MfGames.ToolBuilder/ToolService.cs
Dylan R. E. Moonfire 1f1d0a4d9f feat: provide the names via the tool builder
BREAKING CHANGE: Changed the API.
2021-11-30 20:12:20 -06:00

159 lines
4.7 KiB
C#

using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Threading;
using System.Threading.Tasks;
using MfGames.ToolBuilder.Globals;
using Microsoft.Extensions.Hosting;
using Serilog;
#pragma warning disable Serilog004
// ReSharper disable TemplateIsNotCompileTimeConstantProblem
// ReSharper disable SuspiciousTypeConversion.Global
namespace MfGames.ToolBuilder
{
/// <summary>
/// Implements the command-line shell for generating the static site.
/// </summary>
public class ToolService : IHostedService
{
private readonly IList<ITopCommand> commands;
private readonly ConfigToolGlobalService configService;
private readonly IHostApplicationLifetime lifetime;
private readonly ILogger logger;
private readonly LoggingToolGlobalService loggingService;
private readonly ToolNames names;
public ToolService(
ToolNames names,
ILogger logger,
IHostApplicationLifetime lifetime,
IList<ITopCommand> commands,
ConfigToolGlobalService configService,
LoggingToolGlobalService loggingService)
{
this.names = names;
this.lifetime = lifetime;
this.commands = commands;
this.configService = configService;
this.loggingService = loggingService;
this.logger = logger.ForContext<ToolService>();
}
/// <inheritdoc />
public Task StartAsync(CancellationToken cancellationToken)
{
this.lifetime.ApplicationStarted
.Register(
() =>
{
Task.Run(
async () =>
await this.RunAsync().ConfigureAwait(false),
cancellationToken);
});
return Task.CompletedTask;
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private FakedRootCommand CreateRootCommand()
{
// Create the root command and add in the top-level commands
// underneath it. We can't use the "real" `RootCommand` here because
// it doesn't work in stripped executables (because this is a
// library) so we fake it with a "normal" command.
var root = new FakedRootCommand(this.names);
foreach (var command in this.commands)
{
root.AddCommand((Command)command);
}
// Add the universal options.
this.loggingService.AddOptions(root);
this.configService.AddOptions(root);
// Return the resulting container.
return root;
}
private void OnException(Exception exception, InvocationContext context)
{
if (exception is ToolException toolException)
{
this.logger.Fatal(toolException.Message);
foreach (var message in toolException.Messages)
{
this.logger.Fatal(message);
}
Environment.ExitCode = toolException.ExitCode;
}
else
{
this.logger.Fatal(
exception,
"Unhandled exception!");
Environment.ExitCode = Environment.ExitCode == 0
? 1
: Environment.ExitCode;
}
}
private async Task RunAsync()
{
try
{
// Build the command tree.
FakedRootCommand root = this.CreateRootCommand();
string[] args = FakedRootCommand.GetArguments();
// Execute the command.
this.logger.Verbose(
"Running the command-line arguments: {Arguments}",
args);
CommandLineBuilder builder = new CommandLineBuilder(root)
.UseDefaults()
.UseExceptionHandler(this.OnException);
Parser cli = builder.Build();
int exitCode = await cli
.InvokeAsync(args)
.ConfigureAwait(false);
if (exitCode != 0)
{
Environment.ExitCode = exitCode;
}
}
finally
{
// Stop the application once the work is done.
this.lifetime.StopApplication();
}
}
}
}