using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; using Zio; using Zio.FileSystems; namespace Nitride { /// /// A class that implements a builder pattern for gathering all the /// components of a static website. Once everything is build, then calling /// `Build()` will generate the resulting website. /// public class NitrideBuilder { private readonly string[] arguments; private readonly List> configureContainerCallbacks; /// /// An event that is called after the container is built but before /// the application runs. /// private readonly List> configureSiteCallbacks; private NitrideLoggingBuilder loggingBuilder; public NitrideBuilder(string[] arguments) { this.arguments = arguments; this.loggingBuilder = new NitrideLoggingBuilder(); this.configureSiteCallbacks = new List>(); this.configureContainerCallbacks = new List>(); } /// /// Gets or sets the builder used to set up logging. /// /// public NitrideLoggingBuilder LoggingBuilder { get => this.loggingBuilder; set => this.loggingBuilder = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets or sets the input directory to automatically register as the /// source of the files. If this is not set, then no Zio.IFileSystem /// will be registered and it will have to be done manually. /// public DirectoryInfo? RootDirectory { get; set; } /// /// Generates or builds the resulting website based on the given /// command. /// /// The task once completed. public async Task BuildAsync() { await Host .CreateDefaultBuilder(this.arguments) .UseSerilog() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureServices(this.ConfigureServices) .ConfigureContainer(this.ConfigureContainer) .RunConsoleAsync(); return 0; } /// /// Allows for configuration of the Autofac container to register /// additional types, pipelines, and modules. /// /// The callback to configure the container. /// The builder to chain operations. public NitrideBuilder ConfigureContainer( Action callback) { this.configureContainerCallbacks.Add(callback); return this; } /// /// Overrides the logging configuration for customization. /// /// /// The builder to chain methods. public NitrideBuilder ConfigureLogging( Func callback) { this.loggingBuilder.Callback = callback; return this; } /// /// Registers a callback to be called after the container is built but /// before the application runs. /// /// The callback to register. /// The builder for chaining. public NitrideBuilder ConfigureSite( Action callback) { this.configureSiteCallbacks.Add(callback); return this; } /// /// Sets the root directory to a common value by creating a /// IFileSystem (from Zio) of the root directory and registering it. /// This will be used for both input and output. /// /// /// The path to the directory that represents "/" while /// building. /// /// The builder to chain calls. public NitrideBuilder WithRootDirectory(DirectoryInfo directory) { this.RootDirectory = directory; return this; } private void ConfigureContainer(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. ILogger logger = this.loggingBuilder .Setup(builder) .ForContext(); AppDomain.CurrentDomain.ProcessExit += (_, _) => Log.CloseAndFlush(); // Hook up the rest of the modules. builder.RegisterModule(); // Set up our file system. this.RegisterRootDirectory(logger, builder); // Finish up the registration by running our events. foreach (var callback in this.configureSiteCallbacks) { builder.RegisterBuildCallback(scope => callback(this, scope)); } foreach (var configureContainer in this.configureContainerCallbacks) { configureContainer.Invoke(builder); } } private void ConfigureServices(IServiceCollection services) { services.AddAutofac(); services.AddHostedService(); } private void RegisterRootDirectory( ILogger logger, ContainerBuilder builder) { if (this.RootDirectory == null) { logger.Verbose("No root directory is registered"); return; } logger.Debug( "Setting root directory to {Path}", this.RootDirectory.FullName); var rootFileSystem = new PhysicalFileSystem(); var subFileSystem = new SubFileSystem( rootFileSystem, this.RootDirectory.FullName); builder.RegisterInstance(subFileSystem) .As() .SingleInstance(); } } }