/// <summary> /// Adds a package. /// </summary> /// <param name="packageId">The package identifier.</param> /// <param name="packageSources">The package sources.</param> /// <param name="versionRange">The version range.</param> /// <param name="getLatest">If set to <c>true</c>, the latest version of the package will always be downloaded.</param> /// <param name="allowPrereleaseVersions">If set to <c>true</c>, allow prerelease versions.</param> /// <param name="allowUnlisted">If set to <c>true</c>, allow unlisted versions.</param> /// <param name="exclusive">If set to <c>true</c>, only use the package sources defined for this package.</param> public void AddPackage( string packageId, IEnumerable <string> packageSources = null, string versionRange = null, bool getLatest = false, bool allowPrereleaseVersions = false, bool allowUnlisted = false, bool exclusive = false) { if (_packages.ContainsKey(packageId)) { throw new ArgumentException($"A package with the ID {packageId} has already been added"); } // If this package is a known Wyam extension and no version is specified, set to the current version if (KnownExtension.Values.Values.Any(x => x.PackageId == packageId) && versionRange == null) { versionRange = $"[{Engine.Version}]"; Trace.Verbose($"Added known extension package {packageId} without version, setting version to {versionRange}"); } _packages.Add( packageId, new Package( _currentFramework, packageId, packageSources?.Select(_sourceRepositories.CreateRepository).ToList(), versionRange, getLatest, allowPrereleaseVersions, allowUnlisted, exclusive)); }
internal void InstallPackages() { DirectoryPath packagesPath = GetAbsolutePackagesPath(); Trace.Information($"Installing packages to {packagesPath.FullPath} (using {(UseLocalPackagesFolder ? "local" : "global")} packages folder)"); try { // Add the global default sources if requested if (UseGlobalPackageSources) { _sourceRepositories.AddGlobalDefaults(); } // Get the local repository SourceRepository localRepository = _sourceRepositories.CreateRepository(packagesPath.FullPath); // Get the package manager and repositories WyamFolderNuGetProject nuGetProject = new WyamFolderNuGetProject(_fileSystem, _assemblyLoader, _currentFramework, packagesPath.FullPath); NuGetPackageManager packageManager = new NuGetPackageManager(_sourceRepositories, _settings, packagesPath.FullPath) { PackagesFolderNuGetProject = nuGetProject }; IReadOnlyList <SourceRepository> remoteRepositories = _sourceRepositories.GetDefaultRepositories(); // Resolve all the versions IReadOnlyList <SourceRepository> installationRepositories = remoteRepositories; try { ResolveVersions(localRepository, remoteRepositories); } catch (Exception ex) { Trace.Verbose($"Exception while resolving package versions: {ex.Message}"); Trace.Warning("Error while resolving package versions, attempting without remote repositories"); installationRepositories = new[] { localRepository }; ResolveVersions(localRepository, Array.Empty <SourceRepository>()); } // Install the packages (doing this synchronously since doing it in parallel triggers file lock errors in NuGet on a clean system) try { InstallPackages(packageManager, installationRepositories); } catch (Exception ex) { Trace.Verbose($"Exception while installing packages: {(ex is AggregateException ? string.Join("; ", ((AggregateException)ex).InnerExceptions.Select(x => x.Message)) : ex.Message)}"); Trace.Warning("Error while installing packages, attempting without remote repositories"); InstallPackages(packageManager, new[] { localRepository }); } // Process the package (do this after all packages have been installed) nuGetProject.ProcessAssembliesAndContent(); } catch (Exception ex) { Trace.Verbose($"Unexpected exception while installing packages: {(ex is AggregateException ? string.Join("; ", ((AggregateException)ex).InnerExceptions.Select(x => x.Message)) : ex.Message)}"); Trace.Warning("Error while installing packages, attempting to continue anyway"); } }
// Internal for testing internal void AddRecipePackageAndSetTheme() { if (Recipe == null && !string.IsNullOrEmpty(RecipeName)) { KnownRecipe knownRecipe; if (KnownRecipe.Values.TryGetValue(RecipeName, out knownRecipe)) { Trace.Verbose($"Recipe {RecipeName} was in the lookup of known recipes"); // Make sure we're not ignoring packages if (!IgnoreKnownRecipePackages) { // Add the package, but only if it wasn't added manually if (!string.IsNullOrEmpty(knownRecipe.PackageId) && !PackageInstaller.ContainsPackage(knownRecipe.PackageId)) { PackageInstaller.AddPackage(knownRecipe.PackageId, versionRange: $"[{Engine.Version}]", allowPrereleaseVersions: true); } } else { Trace.Verbose("Ignoring known recipe packages"); } // Set the theme if we don't already have one if (string.IsNullOrEmpty(Theme)) { Theme = knownRecipe.DefaultTheme; } } else { Trace.Verbose($"Recipe {RecipeName} is not in the lookup of known recipes"); } } }
private static void UnhandledExceptionEvent(object sender, UnhandledExceptionEventArgs e) { // Exit with a error exit code Exception exception = e.ExceptionObject as Exception; if (exception != null) { Trace.Critical(exception.Message); Trace.Verbose(exception.ToString()); } Environment.Exit((int)ExitCode.UnhandledError); }
/// <inheritdoc /> public IEnumerable <IDocument> Execute(IReadOnlyList <IDocument> inputs, IExecutionContext context) { // Expire the internal Razor cache if this is a new execution // This needs to be done so that layouts/partials can be re-rendered if they've changed, // otherwise Razor will just use the previously cached version of them if (_executionId != Guid.Empty && _executionId != context.ExecutionId) { RazorService.ExpireChangeTokens(); } _executionId = context.ExecutionId; // Eliminate input documents that we shouldn't process List <IDocument> validInputs = inputs .Where(context, x => _ignorePrefix == null || !x.ContainsKey(Keys.SourceFileName) || !x.FilePath(Keys.SourceFileName).FullPath.StartsWith(_ignorePrefix)) .ToList(); if (validInputs.Count < inputs.Count) { Trace.Information($"Ignoring {inputs.Count - validInputs.Count} inputs due to source file name prefix"); } // Compile and evaluate the pages in parallel return(validInputs.AsParallel().Select(context, input => { Trace.Verbose("Processing Razor for {0}", input.SourceString()); Stream contentStream = context.GetContentStream(); using (Stream inputStream = input.GetStream()) { FilePath viewStartLocationPath = _viewStartPath?.Invoke <FilePath>(input, context); RenderRequest request = new RenderRequest { Input = inputStream, Output = contentStream, BaseType = _basePageType, Context = context, Document = input, LayoutLocation = _layoutPath?.Invoke <FilePath>(input, context)?.FullPath, ViewStartLocation = viewStartLocationPath != null ? GetRelativePath(viewStartLocationPath, context) : null, RelativePath = GetRelativePath(input, context), Model = _model == null ? input : _model.Invoke(input, context), }; RazorService.Render(request); } return context.GetDocument(input, contentStream); })); }
// Internal for testing internal void AddThemePackagesAndPath() { string inputPath = Theme; if (!string.IsNullOrEmpty(Theme)) { KnownTheme knownTheme; if (KnownTheme.Values.TryGetValue(Theme, out knownTheme)) { Trace.Verbose($"Theme {Theme} was in the lookup of known themes"); inputPath = knownTheme.InputPath; // Do a sanity check against the recipe (but only if we didn't explicitly specify one) if (Recipe == null && !string.IsNullOrEmpty(RecipeName) && !string.IsNullOrEmpty(knownTheme.Recipe) && !string.Equals(RecipeName, knownTheme.Recipe, StringComparison.OrdinalIgnoreCase)) { Trace.Warning($"Theme {Theme} is designed for recipe {knownTheme.Recipe} but is being used with recipe {RecipeName}, results may be unexpected"); } // Make sure we're not ignoring theme packages if (!IgnoreKnownThemePackages) { // Add any packages needed for the theme if (knownTheme.PackageIds != null) { foreach (string themePackageId in knownTheme.PackageIds.Where(x => !PackageInstaller.ContainsPackage(x))) { PackageInstaller.AddPackage(themePackageId, allowPrereleaseVersions: true); } } } else { Trace.Verbose("Ignoring known theme packages"); } } else { Trace.Verbose($"Theme {Theme} is not in the lookup of known themes, assuming it's an input path"); } } // Insert the theme path if (!string.IsNullOrEmpty(inputPath)) { _engine.FileSystem.InputPaths.Insert(0, new DirectoryPath(inputPath)); } }
public IEnumerable <IDocument> Execute(IReadOnlyList <IDocument> inputs, IExecutionContext context) { // Register all the MVC and Razor services // In the future, if DI is implemented for all Wyam, the IExecutionContext would be registered as a service // and the IHostingEnviornment would be registered as transient with the execution context provided in ctor IServiceCollection serviceCollection = new ServiceCollection(); IMvcCoreBuilder builder = serviceCollection .AddMvcCore() .AddRazorViewEngine(); builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider(context)); serviceCollection.Configure <RazorViewEngineOptions>(options => { options.ViewLocationExpanders.Add(new ViewLocationExpander()); }); serviceCollection .AddSingleton <ILoggerFactory, TraceLoggerFactory>() .AddSingleton <DiagnosticSource, SilentDiagnosticSource>() .AddSingleton <IHostingEnvironment, HostingEnvironment>() .AddSingleton <ObjectPoolProvider, DefaultObjectPoolProvider>() .AddSingleton <IExecutionContext>(context) .AddSingleton <IBasePageTypeProvider>(new BasePageTypeProvider(_basePageType ?? typeof(RazorPage))) .AddScoped <IMvcRazorHost, RazorHost>(); IServiceProvider services = serviceCollection.BuildServiceProvider(); // Eliminate input documents that we shouldn't process List <IDocument> validInputs = inputs .Where(x => _ignorePrefix == null || !x.ContainsKey(Keys.SourceFileName) || !x.FilePath(Keys.SourceFileName).FullPath.StartsWith(_ignorePrefix)) .ToList(); // Compile and evaluate the pages in parallel IServiceScopeFactory scopeFactory = services.GetRequiredService <IServiceScopeFactory>(); return(validInputs.AsParallel().Select(input => { Trace.Verbose("Compiling Razor for {0}", input.SourceString()); using (var scope = scopeFactory.CreateScope()) { // Get services IRazorViewEngine viewEngine = services.GetRequiredService <IRazorViewEngine>(); IRazorPageActivator pageActivator = services.GetRequiredService <IRazorPageActivator>(); HtmlEncoder htmlEncoder = services.GetRequiredService <HtmlEncoder>(); IRazorPageFactoryProvider pageFactoryProvider = services.GetRequiredService <IRazorPageFactoryProvider>(); IRazorCompilationService razorCompilationService = services.GetRequiredService <IRazorCompilationService>(); IHostingEnvironment hostingEnviornment = services.GetRequiredService <IHostingEnvironment>(); // Compile the view string relativePath = GetRelativePath(input, context); FilePath viewStartLocationPath = _viewStartPath?.Invoke <FilePath>(input, context); string viewStartLocation = viewStartLocationPath != null ? GetRelativePath(viewStartLocationPath, context) : null; string layoutLocation = _layoutPath?.Invoke <FilePath>(input, context)?.FullPath; IView view; using (Stream stream = input.GetStream()) { view = GetViewFromStream(relativePath, stream, viewStartLocation, layoutLocation, viewEngine, pageActivator, htmlEncoder, pageFactoryProvider, hostingEnviornment.WebRootFileProvider, razorCompilationService); } // Render the view Trace.Verbose("Processing Razor for {0}", input.SourceString()); using (StringWriter output = new StringWriter()) { Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext = GetViewContext(scope.ServiceProvider, view, input, context, output); viewContext.View.RenderAsync(viewContext).GetAwaiter().GetResult(); return context.GetDocument(input, output.ToString()); } } })); }
// Internal for testing internal static bool ValidateAbsoluteLink(Uri uri, IExecutionContext context) { // Create a request HttpWebRequest request; try { request = WebRequest.Create(uri) as HttpWebRequest; } catch (NotSupportedException ex) { Trace.Warning($"Skipping absolute link {uri}: {ex.Message}"); return(true); } if (request == null) { Trace.Warning($"Skipping absolute link {uri}: only HTTP/HTTPS links are validated"); return(true); } // Set request properties request.Timeout = 60000; // 60 seconds // Perform request as HEAD HttpWebResponse response; request.Method = "HEAD"; try { response = (HttpWebResponse)request.GetResponse(); response.Close(); } catch (WebException) { response = null; } // Check the status code if (response != null) { if ((int)response.StatusCode >= 100 && (int)response.StatusCode < 400) { Trace.Verbose($"Validated absolute link {uri} with status code {(int)response.StatusCode} {response.StatusCode}"); return(true); } } // Try one more time as GET request.Method = "GET"; try { response = (HttpWebResponse)request.GetResponse(); response.Close(); } catch (WebException ex) { Trace.Warning($"Validation failure for absolute link {uri}: {ex.Message}"); return(false); } // Check the status code if ((int)response.StatusCode >= 100 && (int)response.StatusCode < 400) { Trace.Verbose($"Validated absolute link {uri} with status code {(int)response.StatusCode} {response.StatusCode}"); return(true); } Trace.Warning($"Validation failure for absolute link {uri}: returned status code {(int)response.StatusCode} {response.StatusCode}"); return(false); }
// Internal for testing internal static bool ValidateRelativeLink(Uri uri, IExecutionContext context) { List <FilePath> checkPaths = new List <FilePath>(); // Remove the query string and fragment, if any string normalizedPath = uri.ToString(); if (normalizedPath.Contains("#")) { normalizedPath = normalizedPath.Remove(normalizedPath.IndexOf("#", StringComparison.Ordinal)); } if (normalizedPath.Contains("?")) { normalizedPath = normalizedPath.Remove(normalizedPath.IndexOf("?", StringComparison.Ordinal)); } if (normalizedPath == string.Empty) { return(true); } // Remove the link root if there is one and remove the preceding slash if (context.Settings.DirectoryPath(Keys.LinkRoot) != null && normalizedPath.StartsWith(context.Settings.DirectoryPath(Keys.LinkRoot).FullPath)) { normalizedPath = normalizedPath.Substring(context.Settings.DirectoryPath(Keys.LinkRoot).FullPath.Length); } if (normalizedPath.StartsWith("/")) { normalizedPath = normalizedPath.Length > 1 ? normalizedPath.Substring(1) : string.Empty; } // Add the base path if (normalizedPath != string.Empty) { checkPaths.Add(new FilePath(normalizedPath)); } // Add filenames checkPaths.AddRange(LinkGenerator.DefaultHidePages.Select(x => new FilePath(normalizedPath == string.Empty ? x : $"{normalizedPath}/{x}"))); // Add extensions checkPaths.AddRange(LinkGenerator.DefaultHideExtensions.SelectMany(x => checkPaths.Select(y => y.AppendExtension(x))).ToArray()); // Check all the candidate paths FilePath validatedPath = checkPaths.FirstOrDefault(x => { IFile outputFile; try { outputFile = context.FileSystem.GetOutputFile(x); } catch (Exception ex) { Trace.Warning($"Could not validate path {x.FullPath} for relative link {uri}: {ex.Message}"); return(false); } return(outputFile.Exists); }); if (validatedPath != null) { Trace.Verbose($"Validated relative link {uri} at {validatedPath.FullPath}"); return(true); } Trace.Warning($"Validation failure for relative link {uri}: could not find output file at any of {string.Join(", ", checkPaths.Select(x => x.FullPath))}"); return(false); }
protected override ExitCode RunCommand(Preprocessor preprocessor) { // Get the standard input stream _configOptions.Stdin = StandardInputReader.Read(); // Fix the root folder and other files DirectoryPath currentDirectory = Environment.CurrentDirectory; _configOptions.RootPath = _configOptions.RootPath == null ? currentDirectory : currentDirectory.Combine(_configOptions.RootPath); _logFilePath = _logFilePath == null ? null : _configOptions.RootPath.CombineFile(_logFilePath); _configOptions.ConfigFilePath = _configOptions.RootPath.CombineFile(_configOptions.ConfigFilePath ?? "config.wyam"); // Set up the log file if (_logFilePath != null) { Trace.AddListener(new SimpleFileTraceListener(_logFilePath.FullPath)); } // Get the engine and configurator EngineManager engineManager = EngineManager.Get(preprocessor, _configOptions); if (engineManager == null) { return(ExitCode.CommandLineError); } // Configure and execute if (!engineManager.Configure()) { return(ExitCode.ConfigurationError); } if (_verifyConfig) { Trace.Information("No errors. Exiting."); return(ExitCode.Normal); } Trace.Information($"Root path:{Environment.NewLine} {engineManager.Engine.FileSystem.RootPath}"); Trace.Information($"Input path(s):{Environment.NewLine} {string.Join(Environment.NewLine + " ", engineManager.Engine.FileSystem.InputPaths)}"); Trace.Information($"Output path:{Environment.NewLine} {engineManager.Engine.FileSystem.OutputPath}"); if (!engineManager.Execute()) { return(ExitCode.ExecutionError); } bool messagePump = false; // Start the preview server IDisposable previewServer = null; if (_preview) { messagePump = true; DirectoryPath previewPath = _previewRoot == null ? engineManager.Engine.FileSystem.GetOutputDirectory().Path : engineManager.Engine.FileSystem.GetOutputDirectory(_previewRoot).Path; previewServer = PreviewServer.Start(previewPath, _previewPort, _previewForceExtension); } // Start the watchers IDisposable inputFolderWatcher = null; IDisposable configFileWatcher = null; if (_watch) { messagePump = true; Trace.Information("Watching paths(s) {0}", string.Join(", ", engineManager.Engine.FileSystem.InputPaths)); inputFolderWatcher = new ActionFileSystemWatcher(engineManager.Engine.FileSystem.GetOutputDirectory().Path, engineManager.Engine.FileSystem.GetInputDirectories().Select(x => x.Path), true, "*.*", path => { _changedFiles.Enqueue(path); _messageEvent.Set(); }); if (_configOptions.ConfigFilePath != null) { Trace.Information("Watching configuration file {0}", _configOptions.ConfigFilePath); configFileWatcher = new ActionFileSystemWatcher(engineManager.Engine.FileSystem.GetOutputDirectory().Path, new[] { _configOptions.ConfigFilePath.Directory }, false, _configOptions.ConfigFilePath.FileName.FullPath, path => { FilePath filePath = new FilePath(path); if (_configOptions.ConfigFilePath.Equals(filePath)) { _newEngine.Set(); _messageEvent.Set(); } }); } } // Start the message pump if an async process is running ExitCode exitCode = ExitCode.Normal; if (messagePump) { // 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 var thread = new Thread(() => { Trace.Information("Hit any key to exit"); Console.ReadKey(); _exit.Set(); _messageEvent.Set(); }) { IsBackground = true }; thread.Start(); } // Wait for activity while (true) { _messageEvent.WaitOne(); // Blocks the current thread until a signal if (_exit) { break; } // See if we need a new engine if (_newEngine) { // Get a new engine Trace.Information("Configuration file {0} has changed, re-running", _configOptions.ConfigFilePath); engineManager.Dispose(); engineManager = EngineManager.Get(preprocessor, _configOptions); // Configure and execute if (!engineManager.Configure()) { exitCode = ExitCode.ConfigurationError; break; } Console.WriteLine($"Root path:{Environment.NewLine} {engineManager.Engine.FileSystem.RootPath}"); Console.WriteLine($"Input path(s):{Environment.NewLine} {string.Join(Environment.NewLine + " ", engineManager.Engine.FileSystem.InputPaths)}"); Console.WriteLine($"Root path:{Environment.NewLine} {engineManager.Engine.FileSystem.OutputPath}"); if (!engineManager.Execute()) { exitCode = ExitCode.ExecutionError; break; } // Clear the changed files since we just re-ran string changedFile; while (_changedFiles.TryDequeue(out changedFile)) { } _newEngine.Unset(); } else { // Execute if files have changed HashSet <string> changedFiles = new HashSet <string>(); string changedFile; while (_changedFiles.TryDequeue(out changedFile)) { if (changedFiles.Add(changedFile)) { Trace.Verbose("{0} has changed", changedFile); } } if (changedFiles.Count > 0) { Trace.Information("{0} files have changed, re-executing", changedFiles.Count); if (!engineManager.Execute()) { exitCode = ExitCode.ExecutionError; break; } } } // Check one more time for exit if (_exit) { break; } Trace.Information("Hit any key to exit"); _messageEvent.Reset(); } // Shutdown Trace.Information("Shutting down"); engineManager.Dispose(); inputFolderWatcher?.Dispose(); configFileWatcher?.Dispose(); previewServer?.Dispose(); } return(exitCode); }