private async Task ComposePages( Section section, IReadOnlyCollection <NavigationItem> menu, DocsSiteRouter router, DocsMarkdownService renderer, Func <Path, PipeReader, Task <PipeReader> > preprocessorPipe) { var pageComposer = new PageComposer(_site, section, _cache, _output, _uiBundle, renderer); foreach (var pageItem in section.ContentItems.Where(ci => IsPage(ci.Key, ci.Value))) { await pageComposer.ComposePage(pageItem.Key, pageItem.Value, menu, router, preprocessorPipe); } if (section.Type != "doc") { return; } // check if section has index page var indexPage = section.GetContentItem("index.md"); // if index page exists then don't generate it if (indexPage != null) { return; } // we need a redirect target var redirectToPage = section.IndexPage; await pageComposer.ComposeRedirectPage("index.html", redirectToPage); }
public async Task PrepareAssets(DocsSiteRouter router) { foreach (var(path, contentItem) in _uiBundle.GetContentItems(new Path[] { "**/*.js", "**/*.css", "**/*.png", "**/*.jpg", "**/*.gif" })) { var xref = new Xref(_uiBundle.Version, _uiBundle.Id, path); Path route = router.GenerateRoute(xref) ?? throw new InvalidOperationException($"Could not generate route for '{xref}'."); await using var inputStream = await contentItem.File.OpenRead(); await _output.GetOrCreateDirectory(route.GetDirectoryPath()); var outputFile = await _output.GetOrCreateFile(route); var outputStream = await outputFile.OpenWrite(); await inputStream.CopyToAsync(outputStream); } }
public async Task <ContentItem> ComposePage( Path relativePath, ContentItem page, IReadOnlyCollection <NavigationItem> menu, DocsSiteRouter router, Func <Path, PipeReader, Task <PipeReader> > preprocessorPipe) { var(partialHtmlPage, frontmatter) = await ComposePartialHtmlPage( relativePath, page, router, preprocessorPipe); if (frontmatter == null) { frontmatter = new PageFrontmatter { Template = _uiBundle.DefaultTemplate } } ; var fullHtmlPage = await ComposeFullHtmlPage(partialHtmlPage, frontmatter, menu); return(fullHtmlPage); }
private Task <DocsMarkdownService> BuildMarkdownService(Section section, DocsSiteRouter router) { var context = new DocsMarkdownRenderingContext(_site, section, router); var builder = new MarkdownPipelineBuilder(); builder.Use(new DisplayLinkExtension(context)); return(Task.FromResult(new DocsMarkdownService(builder))); }
public IPageRenderer GetPageRenderer(string template, DocsSiteRouter router) { if (string.IsNullOrEmpty(template)) { template = DefaultTemplate; } var templateHbs = _templates[template]; return(new HandlebarsPageRenderer(templateHbs, _partials, router)); }
public async Task ComposeSection(Section section) { var preprocessorPipe = BuildPreProcessors(section); var router = new DocsSiteRouter(_site, section); var renderer = await BuildMarkdownService(section, router); var menu = await ComposeMenu(section); await ComposeAssets(section, router); await ComposePages(section, menu, router, renderer, preprocessorPipe); }
private async Task ComposeIndexPage(Site site) { var redirectoToPage = site.Definition.IndexPage; string target = string.Empty; if (redirectoToPage.IsXref) { var xref = redirectoToPage.Xref.Value; var targetSection = site.GetSectionByXref(xref); if (targetSection == null) { throw new InvalidOperationException( $"Cannot generate site index page. " + $"Target section of '{redirectoToPage}' not found."); } var router = new DocsSiteRouter(site, targetSection); target = router.GenerateRoute(xref) ?? string.Empty; } else { target = redirectoToPage.Uri ?? string.Empty; } var generatedHtml = string.Format( PageComposer.RedirectPageHtml, site.BasePath, target); // create output dir for page Path targetFilePath = "index.html"; if (targetFilePath == new Path(target)) { throw new InvalidOperationException( $"Cannot generate a index.html redirect page '{targetFilePath}'. " + $"Redirect would point to same file as the generated file and would" + "end in a endless loop"); } await _output.GetOrCreateDirectory(targetFilePath.GetDirectoryPath()); // create output file var outputFile = await _output.GetOrCreateFile(targetFilePath); await using var outputStream = await outputFile.OpenWrite(); await using var writer = new StreamWriter(outputStream); await writer.WriteAsync(generatedHtml); }
public PageComposer( Site site, Section section, IFileSystem cache, IFileSystem output, IUiBundle uiBundle, DocsMarkdownService renderer) { _site = site; _section = section; _cache = cache; _output = output; _uiBundle = uiBundle; _router = new DocsSiteRouter(site, section); _docsMarkdownService = renderer; }
private async Task <IReadOnlyCollection <NavigationItem> > ComposeMenu(Section section) { var items = new List <NavigationItem>(); foreach (var naviFileLink in section.Definition.Nav) { if (naviFileLink.Xref == null) { throw new NotSupportedException("External navigation file links are not supported"); } var xref = naviFileLink.Xref.Value; var targetSection = _site.GetSectionByXref(xref, section); if (targetSection == null) { throw new InvalidOperationException($"Invalid navigation file link {naviFileLink}. Section not found."); } var navigationFileItem = targetSection.GetContentItem(xref.Path); if (navigationFileItem == null) { throw new InvalidOperationException($"Invalid navigation file link {naviFileLink}. Path not found."); } await using var fileStream = await navigationFileItem.File.OpenRead(); using var reader = new StreamReader(fileStream); var text = await reader.ReadToEndAsync(); // override context so each navigation file is rendered in the context of the owning section var router = new DocsSiteRouter(_site, targetSection); var renderer = new DocsMarkdownService(new DocsMarkdownRenderingContext(_site, targetSection, router)); var builder = new NavigationBuilder(renderer, router); var fileItems = builder.Add(new string[] { text }) .Build(); items.AddRange(fileItems); } return(items); }
private async Task <(ContentItem Page, PageFrontmatter?Frontmatter)> ComposePartialHtmlPage( Path relativePath, ContentItem page, DocsSiteRouter router, Func <Path, PipeReader, Task <PipeReader> > preprocessorPipe) { Path outputPath = router.GenerateRoute(new Xref(page.Version, _section.Id, relativePath)) ?? throw new InvalidOperationException($"Could not generate output path for '{page}'"); // create output dir for page await _cache.GetOrCreateDirectory(outputPath.GetDirectoryPath()); // create output file var outputFile = await _cache.GetOrCreateFile(outputPath); // open file streams await using var inputStream = await page.File.OpenRead(); // stream for preprocessed content await using var processedStream = new MemoryStream(); // process preprocessor directives var reader = await preprocessorPipe(relativePath, PipeReader.Create(inputStream)); var writer = PipeWriter.Create(processedStream, new StreamPipeWriterOptions(leaveOpen: true)); await reader.CopyToAsync(writer); await reader.CompleteAsync(); await writer.CompleteAsync(); processedStream.Position = 0; // render markdown await using var outputStream = await outputFile.OpenWrite(); var frontmatter = await _docsMarkdownService.RenderPage(processedStream, outputStream); return(page.WithFile(outputFile, "text/html"), frontmatter); }
private async Task ComposeAssets(Section section, DocsSiteRouter router) { // compose assets from sections foreach (var(relativePath, assetItem) in section.ContentItems.Where(ci => IsAsset(ci.Key, ci.Value))) { // open file streams await using var inputStream = await assetItem.File.OpenRead(); // create output dir for page Path outputPath = router.GenerateRoute(new Xref(assetItem.Version, section.Id, relativePath)) ?? throw new InvalidOperationException($"Could not generate output path for '{outputPath}'."); await _output.GetOrCreateDirectory(outputPath.GetDirectoryPath()); // create output file var outputFile = await _output.GetOrCreateFile(outputPath); await using var outputStream = await outputFile.OpenWrite(); await inputStream.CopyToAsync(outputStream); } }
public HandlebarsPageRenderer(string templateHbs, IReadOnlyDictionary <string, string> partials, DocsSiteRouter router) { _templateHbs = templateHbs; _handlebars = Handlebars.Create(); _handlebars.RegisterHelper("link_to", (output, options, context, arguments) => { if (arguments.Length != 1) { throw new InvalidOperationException($"link_to requires one argument of type DisplayLink"); } var target = arguments[0]; string?url = null; string?title = null; if (target is DisplayLink displayLink) { title = displayLink.Title ?? ""; url = displayLink.Link.Xref != null ? router.GenerateRoute(displayLink.Link.Xref.Value) : displayLink.Link.Uri; } if (url == null) { url = "[TODO: MISSING LINK TARGET]"; } options.Template(output, new { title, url }); }); _handlebars.RegisterHelper("xref", (output, options, context, arguments) => { if (arguments.Length != 1) { throw new InvalidOperationException($"xref requires one argument of type xref"); } var target = arguments[0]; string?url = null; if (target is string xrefStr) { target = LinkParser.Parse(xrefStr); } if (target is Link link) { if (link.IsExternal) { url = link.Uri; } else { target = link.Xref !.Value; }