/// <inheritdoc/> public async Task <int> RunAsync() { // Remove the synchronization context await default(SynchronizationContextRemover); // Populate the class catalog (if we haven't already) _classCatalog.Populate(); // Run bootstrapper configurators first Configurators.Configure <IConfigurableBootstrapper>(this); Configurators.Configure <IBootstrapper>(this); // Run the configuration configurator and get the configuration root SettingsConfigurationProvider settingsProvider = new SettingsConfigurationProvider(); IConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); ConfigurableConfiguration configurableConfiguration = new ConfigurableConfiguration(configurationBuilder); Configurators.Configure(configurableConfiguration); configurationBuilder.Add(settingsProvider); IConfigurationRoot configurationRoot = configurationBuilder.Build(); ConfigurationSettings configurationSettings = new ConfigurationSettings(settingsProvider, configurationRoot); // Create the service collection IServiceCollection serviceCollection = CreateServiceCollection() ?? new ServiceCollection(); serviceCollection.TryAddSingleton <IConfigurableBootstrapper>(this); serviceCollection.TryAddSingleton <IBootstrapper>(this); serviceCollection.TryAddSingleton(_classCatalog); // The class catalog is retrieved later for deferred logging once a service provider is built serviceCollection.TryAddSingleton <IConfiguration>(configurationRoot); // Run configurators on the service collection ConfigurableServices configurableServices = new ConfigurableServices(serviceCollection, configurationRoot); Configurators.Configure(configurableServices); // Add simple logging to make sure it's available in commands before the engine adds in, // but add it after the configurators have a chance to configure logging serviceCollection.AddLogging(); // Create the stand-alone command line service container and register a few types needed for the CLI CommandServiceTypeRegistrar registrar = new CommandServiceTypeRegistrar(); registrar.RegisterInstance(typeof(IConfigurationSettings), configurationSettings); registrar.RegisterInstance(typeof(IConfigurationRoot), configurationRoot); registrar.RegisterInstance(typeof(IServiceCollection), serviceCollection); registrar.RegisterInstance(typeof(IConfiguratorCollection), Configurators); registrar.RegisterInstance(typeof(IBootstrapper), this); // Create the command line parser and run the command ICommandApp app = _getCommandApp(registrar); app.Configure(commandConfigurator => { commandConfigurator.ValidateExamples(); ConfigurableCommands configurableCommands = new ConfigurableCommands(commandConfigurator); Configurators.Configure(configurableCommands); }); int exitCode = await app.RunAsync(Arguments); // Dispose all instances of the console logger to flush the message queue and stop the listening thread ConsoleLoggerProvider.DisposeAll(); return(exitCode); }
protected override async Task <int> ExecuteEngineAsync( CommandContext commandContext, PreviewCommandSettings commandSettings, IEngineManager engineManager) { ExitCode exitCode = ExitCode.Normal; ILogger logger = engineManager.Engine.Services.GetRequiredService <ILogger <Bootstrapper> >(); // Execute the engine for the first time using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource()) { if (!await engineManager.ExecuteAsync(cancellationTokenSource)) { return((int)ExitCode.ExecutionError); } } // Start the preview server Dictionary <string, string> contentTypes = commandSettings.ContentTypes?.Length > 0 ? GetContentTypes(commandSettings.ContentTypes) : new Dictionary <string, string>(); ILoggerProvider loggerProvider = engineManager.Engine.Services.GetRequiredService <ILoggerProvider>(); IDirectory outputDirectory = engineManager.Engine.FileSystem.GetOutputDirectory(); Server previewServer = null; if (outputDirectory.Exists) { previewServer = await StartPreviewServerAsync( outputDirectory.Path, commandSettings.Port, commandSettings.ForceExt, commandSettings.VirtualDirectory, !commandSettings.NoReload, contentTypes, loggerProvider, logger); } // Start the watchers ActionFileSystemWatcher inputFolderWatcher = null; if (!commandSettings.NoWatch) { logger.LogInformation("Watching paths(s) {0}", string.Join(", ", engineManager.Engine.FileSystem.InputPaths)); inputFolderWatcher = new ActionFileSystemWatcher( outputDirectory.Path, engineManager.Engine.FileSystem.GetInputDirectories().Select(x => x.Path), true, "*.*", path => { _changedFiles.Enqueue(path); _messageEvent.Set(); }); } // Start the message pump // Only wait for a key if console input has not been redirected, otherwise it's on the caller to exit if (!Console.IsInputRedirected) { // Start the key listening thread Thread thread = new Thread(() => { logger.LogInformation("Hit Ctrl-C to exit"); Console.TreatControlCAsInput = true; while (true) { // Would have preferred to use Console.CancelKeyPress, but that bubbles up to calling batch files // The (ConsoleKey)3 check is to support a bug in VS Code: https://github.com/Microsoft/vscode/issues/9347 ConsoleKeyInfo consoleKey = Console.ReadKey(true); if (consoleKey.Key == (ConsoleKey)3 || (consoleKey.Key == ConsoleKey.C && (consoleKey.Modifiers & ConsoleModifiers.Control) != 0)) { _exit.Set(); _messageEvent.Set(); break; } } }) { IsBackground = true }; thread.Start(); } // Wait for activity while (true) { _messageEvent.WaitOne(); // Blocks the current thread until a signal if (_exit) { break; } // Execute if files have changed HashSet <string> changedFiles = new HashSet <string>(); while (_changedFiles.TryDequeue(out string changedFile)) { if (changedFiles.Add(changedFile)) { logger.LogDebug($"{changedFile} has changed"); } } if (changedFiles.Count > 0) { logger.LogInformation($"{changedFiles.Count} files have changed, re-executing"); // Reset caches when an error occurs during the previous preview string existingResetCacheSetting = null; bool setResetCacheSetting = false; if (exitCode == ExitCode.ExecutionError) { existingResetCacheSetting = engineManager.Engine.Settings.GetString(Keys.ResetCache); setResetCacheSetting = true; ConfigurationSettings[Keys.ResetCache] = "true"; } // If there was an execution error due to reload, keep previewing but clear the cache using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource()) { exitCode = await engineManager.ExecuteAsync(cancellationTokenSource) ? ExitCode.Normal : ExitCode.ExecutionError; } // Reset the reset cache setting after removing it if (setResetCacheSetting) { if (existingResetCacheSetting == null) { ConfigurationSettings.Remove(Keys.ResetCache); } { ConfigurationSettings[Keys.ResetCache] = existingResetCacheSetting; } } if (previewServer == null) { if (outputDirectory.Exists) { previewServer = await StartPreviewServerAsync( outputDirectory.Path, commandSettings.Port, commandSettings.ForceExt, commandSettings.VirtualDirectory, !commandSettings.NoReload, contentTypes, loggerProvider, logger); } } else { await previewServer.TriggerReloadAsync(); } } // Check one more time for exit if (_exit) { break; } logger.LogInformation("Hit Ctrl-C to exit"); _messageEvent.Reset(); } // Shutdown logger.LogInformation("Shutting down"); inputFolderWatcher?.Dispose(); previewServer?.Dispose(); return((int)exitCode); }
protected override async Task <int> ExecuteEngineAsync( CommandContext commandContext, PreviewCommandSettings commandSettings, IEngineManager engineManager) { SetPipelines(commandContext, commandSettings, engineManager); ExitCode exitCode = ExitCode.Normal; ILogger logger = engineManager.Engine.Services.GetRequiredService <ILogger <Bootstrapper> >(); // Execute the engine for the first time using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource()) { if (!await engineManager.ExecuteAsync(cancellationTokenSource)) { return((int)ExitCode.ExecutionError); } } // Start the preview server Dictionary <string, string> contentTypes = commandSettings.ContentTypes?.Length > 0 ? GetContentTypes(commandSettings.ContentTypes) : new Dictionary <string, string>(); ILoggerProvider loggerProvider = engineManager.Engine.Services.GetRequiredService <ILoggerProvider>(); IDirectory outputDirectory = engineManager.Engine.FileSystem.GetOutputDirectory(); Server previewServer = null; if (outputDirectory.Exists) { previewServer = await StartPreviewServerAsync( outputDirectory.Path, commandSettings.Port, commandSettings.ForceExt, commandSettings.VirtualDirectory, !commandSettings.NoReload, contentTypes, loggerProvider, logger); } // Start the watchers ActionFileSystemWatcher inputFolderWatcher = null; if (!commandSettings.NoWatch) { logger.LogInformation("Watching paths(s) {0}", string.Join(", ", engineManager.Engine.FileSystem.InputPaths)); inputFolderWatcher = new ActionFileSystemWatcher( outputDirectory.Path, engineManager.Engine.FileSystem.GetInputDirectories().Select(x => x.Path), true, "*.*", path => { _changedFiles.Enqueue(path); _messageEvent.Set(); }); } // Start the message pump CommandUtilities.WaitForControlC( () => { _exit.Set(); _messageEvent.Set(); }, logger); // Wait for activity while (true) { _messageEvent.WaitOne(); // Blocks the current thread until a signal if (_exit) { break; } // Execute if files have changed HashSet <string> changedFiles = new HashSet <string>(); while (_changedFiles.TryDequeue(out string changedFile)) { if (changedFiles.Add(changedFile)) { logger.LogDebug($"{changedFile} has changed"); } } if (changedFiles.Count > 0) { logger.LogInformation($"{changedFiles.Count} files have changed, re-executing"); // Reset caches when an error occurs during the previous preview string existingResetCacheSetting = null; bool setResetCacheSetting = false; if (exitCode == ExitCode.ExecutionError) { existingResetCacheSetting = engineManager.Engine.Settings.GetString(Keys.ResetCache); setResetCacheSetting = true; ConfigurationSettings[Keys.ResetCache] = "true"; } // If there was an execution error due to reload, keep previewing but clear the cache using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource()) { exitCode = await engineManager.ExecuteAsync(cancellationTokenSource) ? ExitCode.Normal : ExitCode.ExecutionError; } // Reset the reset cache setting after removing it if (setResetCacheSetting) { if (existingResetCacheSetting == null) { ConfigurationSettings.Remove(Keys.ResetCache); } { ConfigurationSettings[Keys.ResetCache] = existingResetCacheSetting; } } if (previewServer == null) { if (outputDirectory.Exists) { previewServer = await StartPreviewServerAsync( outputDirectory.Path, commandSettings.Port, commandSettings.ForceExt, commandSettings.VirtualDirectory, !commandSettings.NoReload, contentTypes, loggerProvider, logger); } } else { await previewServer.TriggerReloadAsync(); } } // Check one more time for exit if (_exit) { break; } logger.LogInformation("Hit Ctrl-C to exit"); _messageEvent.Reset(); } // Shutdown logger.LogInformation("Shutting down"); inputFolderWatcher?.Dispose(); previewServer?.Dispose(); return((int)exitCode); }