Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 47 additions & 19 deletions src/Blake.BuildTools/Generator/ContentIndexBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Text;
using Microsoft.Extensions.Logging;

namespace Blake.BuildTools.Generator;

public static class ContentIndexBuilder
{
public static void WriteIndex(string outputPath, List<PageModel> allPages)
public static void WriteIndex(string outputPath, List<PageModel> allPages, bool continueOnError, ILogger logger)
{
var sb = new StringBuilder();

Expand All @@ -17,26 +18,53 @@ public static void WriteIndex(string outputPath, List<PageModel> allPages)

foreach (var page in allPages)
{
sb.AppendLine(" new PageModel");
sb.AppendLine(" {");
sb.AppendLine($" Id = @\"{page.Id}\",");
sb.AppendLine($" Title = @\"{page.Title}\",");
sb.AppendLine($" Slug = @\"{page.Slug}\",");
sb.AppendLine($" Description = @\"{page.Description}\",");
if (page.Date.HasValue)
sb.AppendLine($" Date = new DateTime({page.Date.Value.Year}, {page.Date.Value.Month}, {page.Date.Value.Day}),");
sb.AppendLine($" Draft = {page.Draft.ToString().ToLowerInvariant()},");
sb.AppendLine($" IconIdentifier = @\"{page.IconIdentifier}\",");
sb.AppendLine($" Tags = new List<string> {{ {string.Join(", ", page.Tags.Select(t => $"\"{t}\""))} }},");
sb.AppendLine($" Image = @\"{page.Image}\",");
sb.AppendLine(" Metadata = new Dictionary<string, string>");
sb.AppendLine(" {");
foreach (var kvp in page.Metadata)
var tags = page.Tags ?? [];

try
{
sb.AppendLine($" [\"{kvp.Key}\"] = \"{kvp.Value}\",");
sb.AppendLine(" new PageModel");
sb.AppendLine(" {");
sb.AppendLine($" Id = @\"{page.Id}\",");
sb.AppendLine($" Title = @\"{page.Title}\",");
sb.AppendLine($" Slug = @\"{page.Slug}\",");
sb.AppendLine($" Description = @\"{page.Description}\",");
if (page.Date.HasValue)
sb.AppendLine(
$" Date = new DateTime({page.Date.Value.Year}, {page.Date.Value.Month}, {page.Date.Value.Day}),");
sb.AppendLine($" Draft = {page.Draft.ToString().ToLowerInvariant()},");
sb.AppendLine($" IconIdentifier = @\"{page.IconIdentifier}\",");
if (tags.Count > 0)
{
sb.AppendLine(
$" Tags = new List<string> {{ {string.Join(", ", tags.Select(t => $"\"{t}\""))} }},");
}
else
{
sb.AppendLine($" Tags = new List<string> {{ }},");
}
sb.AppendLine($" Image = @\"{page.Image}\",");
sb.AppendLine(" Metadata = new Dictionary<string, string>");
sb.AppendLine(" {");
foreach (var kvp in page.Metadata)
{
sb.AppendLine($" [\"{kvp.Key}\"] = \"{kvp.Value}\",");
}

sb.AppendLine(" }");
sb.AppendLine(" },");
}
catch (Exception e)
{
if (continueOnError)
{
logger.LogWarning(e, "Failed to write page to index. Continuing with next page. Details: ID: {pageId}, Title: {pageTitle}", page.Id, page.Title);
}
else
{
logger.LogError(e, "Failed to write page to index. Details: ID: {pageId}, Title: {pageTitle}", page.Id, page.Title);
throw;
}
}
sb.AppendLine(" }");
sb.AppendLine(" },");
}

sb.AppendLine(" };\n}");
Expand Down
95 changes: 61 additions & 34 deletions src/Blake.BuildTools/Generator/SiteGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public static async Task BuildAsync(GenerationOptions options, ILogger logger, C
ProjectPath = Directory.GetCurrentDirectory(),
OutputPath = Path.Combine(Directory.GetCurrentDirectory(), ".generated"),
};

var continueOnError = options.Arguments.Contains("--continueOnError") || options.Arguments.Contains("-ce");

logger.LogInformation("🔧 Building site from project path: {OptionsProjectPath}", options.ProjectPath);
logger.LogInformation("📂 Output path: {OptionsOutputPath}", options.OutputPath);
Expand Down Expand Up @@ -68,7 +70,7 @@ public static async Task BuildAsync(GenerationOptions options, ILogger logger, C
logger.LogDebug("No plugins loaded.");
}

await BakeContent(context, options, logger, cancellationToken);
await BakeContent(context, options, continueOnError, logger, cancellationToken);

// Run AfterBakeAsync for each plugin
if (plugins.Count > 0)
Expand All @@ -92,7 +94,7 @@ public static async Task BuildAsync(GenerationOptions options, ILogger logger, C
{
try
{
await File.WriteAllTextAsync(generatedPage.OutputPath, generatedPage.RazorHtml);
await File.WriteAllTextAsync(generatedPage.OutputPath, generatedPage.RazorHtml, cancellationToken);
logger.LogDebug("✅ Successfully wrote page: {GeneratedPageOutputPath}", generatedPage.OutputPath);
}
catch (Exception ex)
Expand All @@ -102,7 +104,7 @@ public static async Task BuildAsync(GenerationOptions options, ILogger logger, C
}

// Write content index
ContentIndexBuilder.WriteIndex(options.OutputPath, [.. context.GeneratedPages.Select(gp => gp.Page)]);
ContentIndexBuilder.WriteIndex(options.OutputPath, [.. context.GeneratedPages.Select(gp => gp.Page)], continueOnError, logger);
logger.LogDebug("✅ Generated content index in {OptionsOutputPath}", options.OutputPath);
}

Expand Down Expand Up @@ -281,7 +283,12 @@ private static async Task<BlakeContext> GetBlakeContext(GenerationOptions option
return context;
}

private static async Task BakeContent(BlakeContext context, GenerationOptions options, ILogger logger, CancellationToken cancellationToken)
private static async Task BakeContent(
BlakeContext context,
GenerationOptions options,
bool continueOnError,
ILogger logger,
CancellationToken cancellationToken)
{
// Bake: Process each markdown file and generate Razor pages
var mdPipeline = context.PipelineBuilder.Build();
Expand All @@ -292,49 +299,69 @@ private static async Task BakeContent(BlakeContext context, GenerationOptions op

foreach (var mdPage in context.MarkdownPages)
{
sw.GetStringBuilder().Clear();
var mdContent = mdPage.RawMarkdown;
try
{
sw.GetStringBuilder().Clear();
var mdContent = mdPage.RawMarkdown;

var frontmatter = FrontmatterHelper.ParseFrontmatter(mdContent, cleanedContent: out _);
var page = FrontmatterHelper.MapToMetadata<PageModel>(frontmatter);
var frontmatter = FrontmatterHelper.ParseFrontmatter(mdContent, cleanedContent: out _);
var page = FrontmatterHelper.MapToMetadata<PageModel>(frontmatter);

var fileName = Path.GetFileNameWithoutExtension(mdPage.MdPath) ?? "index";
var folder = Path.GetDirectoryName(mdPage.MdPath)?.Replace(options.ProjectPath, string.Empty).Trim(Path.DirectorySeparatorChar) ?? string.Empty;
var fileName = Path.GetFileNameWithoutExtension(mdPage.MdPath) ?? "index";
var folder =
Path.GetDirectoryName(mdPage.MdPath)?.Replace(options.ProjectPath, string.Empty)
.Trim(Path.DirectorySeparatorChar) ?? string.Empty;

if (page.Draft && !options.IncludeDrafts)
{
logger.LogInformation("⚠️ Skipping draft page: {FileName} in {Folder}", fileName, folder);
continue;
}
if (page.Draft && !options.IncludeDrafts)
{
logger.LogInformation("⚠️ Skipping draft page: {FileName} in {Folder}", fileName, folder);
continue;
}

page.Slug = mdPage.Slug;
page.Slug = mdPage.Slug;

//var parsedContent = Markdown.ToHtml(mdContent, mdPipeline);
// 🔄 Parse the markdown
var document = Markdig.Parsers.MarkdownParser.Parse(mdContent, mdPipeline);
//var parsedContent = Markdown.ToHtml(mdContent, mdPipeline);
// 🔄 Parse the markdown
var document = Markdig.Parsers.MarkdownParser.Parse(mdContent, mdPipeline);

// 🖋️ Render it
renderer.Render(document);
renderer.Writer.Flush();
// 🖋️ Render it
renderer.Render(document);
await renderer.Writer.FlushAsync(cancellationToken);

// 🔙 Get the rendered HTML
var renderedHtml = sw.ToString();
// 🔙 Get the rendered HTML
var renderedHtml = sw.ToString();

var generatedRazor = RazorPageBuilder.BuildRazorPage(mdPage.TemplatePath, renderedHtml, mdPage.Slug, page);
var generatedRazor =
RazorPageBuilder.BuildRazorPage(mdPage.TemplatePath, renderedHtml, mdPage.Slug, page);

var outputDir = Path.Combine(options.OutputPath, folder.ToLowerInvariant());
Directory.CreateDirectory(outputDir);
var outputDir = Path.Combine(options.OutputPath, folder.ToLowerInvariant());
Directory.CreateDirectory(outputDir);

// create output filename - remove spaces or dashes, and convert to PascalCase instead
// Razor filenames must be PascalCase and cannot contain spaces or dashes; this avoids enforcing this convention in markdown files
var fileNameParts = fileName.Split([' ', '-'], StringSplitOptions.RemoveEmptyEntries);
var outputFileName = string.Join("", fileNameParts.Select(part => char.ToUpperInvariant(part[0]) + part.Substring(1).ToLowerInvariant()));
// create output filename - remove spaces or dashes, and convert to PascalCase instead
// Razor filenames must be PascalCase and cannot contain spaces or dashes; this avoids enforcing this convention in markdown files
var fileNameParts = fileName.Split([' ', '-'], StringSplitOptions.RemoveEmptyEntries);
var outputFileName = string.Join("",
fileNameParts.Select(part =>
char.ToUpperInvariant(part[0]) + part.Substring(1).ToLowerInvariant()));

var outputPath = Path.Combine(outputDir, $"{outputFileName}.razor");
var outputPath = Path.Combine(outputDir, $"{outputFileName}.razor");

logger.LogInformation("✅ Generated page: {OutputPath}", outputPath);
logger.LogInformation("✅ Generated page: {OutputPath}", outputPath);

context.GeneratedPages.Add(new GeneratedPage(page, outputPath, generatedRazor));
context.GeneratedPages.Add(new GeneratedPage(page, outputPath, generatedRazor));
}
catch (Exception e)
{
logger.LogError(e, "❌ Error processing markdown file: {MdPath}", mdPage.MdPath);
if (continueOnError)
{
logger.LogWarning("⚠️ Continuing to process other markdown files despite the error.");
}
else
{
throw; // rethrow if not continuing on error
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Blake.CLI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ private static void ShowHelp()
Console.WriteLine(" --disableDefaultRenderers, -dr Disable the built-in Bootstrap container renderers");
Console.WriteLine(" --includeDrafts Bakes markdown files that contain 'draft: true' in the frontmatter (they are skipped by default)");
Console.WriteLine(" --clean, -cl Deletes the .generated folder before re-generating site content");
Console.WriteLine(" --continueOnError, -ce Continues baking even if some pages fail to generate. By default, the process stops on the first error.");
Console.WriteLine();
Console.WriteLine(" new <PATH> Generates a new Blake site");
Console.WriteLine(" Options:");
Expand Down
Loading