Merge branch 'master' into write-llvm-0

This commit is contained in:
2024-08-23 20:49:00 +08:00
143 changed files with 894 additions and 222 deletions

View File

@@ -0,0 +1,36 @@
using System.CommandLine.Binding;
using System.Text.Json;
using Microsoft.Extensions.Options;
using YaeBlog.Core.Models;
namespace YaeBlog.Commands.Binders;
public sealed class BlogOptionsBinder : BinderBase<IOptions<BlogOptions>>
{
protected override IOptions<BlogOptions> GetBoundValue(BindingContext bindingContext)
{
bindingContext.AddService<IOptions<BlogOptions>>(_ =>
{
FileInfo settings = new(Path.Combine(Environment.CurrentDirectory, "appsettings.json"));
if (!settings.Exists)
{
throw new InvalidOperationException("Failed to load YaeBlog configurations.");
}
using StreamReader reader = settings.OpenText();
using JsonDocument document = JsonDocument.Parse(reader.ReadToEnd());
JsonElement root = document.RootElement;
JsonElement optionSection = root.GetProperty(BlogOptions.OptionName);
BlogOptions? result = optionSection.Deserialize<BlogOptions>();
if (result is null)
{
throw new InvalidOperationException("Failed to load YaeBlog configuration in appsettings.json.");
}
return new OptionsWrapper<BlogOptions>(result);
});
return bindingContext.GetRequiredService<IOptions<BlogOptions>>();
}
}

View File

@@ -0,0 +1,32 @@
using System.CommandLine.Binding;
using Microsoft.Extensions.Options;
using YaeBlog.Core.Abstractions;
using YaeBlog.Core.Models;
using YaeBlog.Core.Services;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace YaeBlog.Commands.Binders;
public sealed class EssayScanServiceBinder : BinderBase<IEssayScanService>
{
protected override IEssayScanService GetBoundValue(BindingContext bindingContext)
{
bindingContext.AddService<IEssayScanService>(provider =>
{
DeserializerBuilder deserializerBuilder = new();
deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
deserializerBuilder.IgnoreUnmatchedProperties();
SerializerBuilder serializerBuilder = new();
serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
IOptions<BlogOptions> options = provider.GetRequiredService<IOptions<BlogOptions>>();
ILogger<EssayScanService> logger = provider.GetRequiredService<ILogger<EssayScanService>>();
return new EssayScanService(serializerBuilder.Build(), deserializerBuilder.Build(), options, logger);
});
return bindingContext.GetRequiredService<IEssayScanService>();
}
}

View File

@@ -0,0 +1,18 @@
using System.CommandLine.Binding;
namespace YaeBlog.Commands.Binders;
public sealed class LoggerBinder<T> : BinderBase<ILogger<T>>
{
protected override ILogger<T> GetBoundValue(BindingContext bindingContext)
{
bindingContext.AddService(_ => LoggerFactory.Create(builder => builder.AddConsole()));
bindingContext.AddService<ILogger<T>>(provider =>
{
ILoggerFactory factory = provider.GetRequiredService<ILoggerFactory>();
return factory.CreateLogger<T>();
});
return bindingContext.GetRequiredService<ILogger<T>>();
}
}

View File

@@ -1,30 +0,0 @@
using System.CommandLine.Binding;
using System.Text.Json;
using YaeBlog.Core.Models;
namespace YaeBlog.Commands;
public sealed class BlogOptionsBinder : BinderBase<BlogOptions>
{
protected override BlogOptions GetBoundValue(BindingContext bindingContext)
{
FileInfo settings = new(Path.Combine(Environment.CurrentDirectory, "appsettings.json"));
if (!settings.Exists)
{
throw new InvalidOperationException("Failed to load YaeBlog configurations.");
}
using StreamReader reader = settings.OpenText();
using JsonDocument document = JsonDocument.Parse(reader.ReadToEnd());
JsonElement root = document.RootElement;
JsonElement optionSection = root.GetProperty(BlogOptions.OptionName);
BlogOptions? result = optionSection.Deserialize<BlogOptions>();
if (result is null)
{
throw new InvalidOperationException("Failed to load YaeBlog configuration in appsettings.json.");
}
return result;
}
}

View File

@@ -1,12 +1,15 @@
using System.CommandLine;
using YaeBlog.Commands.Binders;
using YaeBlog.Components;
using YaeBlog.Core.Extensions;
using YaeBlog.Core.Models;
using YaeBlog.Core.Services;
namespace YaeBlog.Commands;
public static class CommandExtensions
{
public static Command AddServeCommand(this RootCommand rootCommand)
public static void AddServeCommand(this RootCommand rootCommand)
{
Command serveCommand = new("serve", "Start http server.");
rootCommand.AddCommand(serveCommand);
@@ -20,6 +23,7 @@ public static class CommandExtensions
builder.Services.AddControllers();
builder.Services.AddBlazorBootstrap();
builder.AddYaeBlog();
builder.AddServer();
WebApplication application = builder.Build();
@@ -34,11 +38,40 @@ public static class CommandExtensions
CancellationToken token = context.GetCancellationToken();
await application.RunAsync(token);
});
return rootCommand;
}
public static Command AddNewCommand(this RootCommand rootCommand)
public static void AddWatchCommand(this RootCommand rootCommand)
{
Command command = new("watch", "Start a blog watcher that re-render when file changes.");
rootCommand.AddCommand(command);
command.SetHandler(async context =>
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddControllers();
builder.Services.AddBlazorBootstrap();
builder.AddYaeBlog();
builder.AddWatcher();
WebApplication application = builder.Build();
application.UseStaticFiles();
application.UseAntiforgery();
application.UseYaeBlog();
application.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
application.MapControllers();
CancellationToken token = context.GetCancellationToken();
await application.RunAsync(token);
});
}
public static void AddNewCommand(this RootCommand rootCommand)
{
Command newCommand = new("new", "Create a new blog file and image directory.");
rootCommand.AddCommand(newCommand);
@@ -46,45 +79,40 @@ public static class CommandExtensions
Argument<string> filenameArgument = new(name: "blog name", description: "The created blog filename.");
newCommand.AddArgument(filenameArgument);
newCommand.SetHandler(async (file, blogOptions) =>
newCommand.SetHandler(async (file, _, _, essayScanService) =>
{
await essayScanService.SaveBlogContent(new BlogContent
{
FileName = file,
FileContent = string.Empty,
Metadata = new MarkdownMetadata { Title = file, Date = DateTime.Now }
});
Console.WriteLine($"Created new blog '{file}.");
}, filenameArgument, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(),
new EssayScanServiceBinder());
}
public static void AddListCommand(this RootCommand rootCommand)
{
Command command = new("list", "List all blogs");
rootCommand.Add(command);
command.SetHandler(async (_, _, essyScanService) =>
{
string fileWithExtension;
if (file.EndsWith(".md"))
BlogContents contents = await essyScanService.ScanContents();
Console.WriteLine($"All {contents.Posts.Count} Posts:");
foreach (BlogContent content in contents.Posts)
{
fileWithExtension = file;
file = fileWithExtension[..fileWithExtension.LastIndexOf('.')];
}
else
{
fileWithExtension = file + ".md";
Console.WriteLine($" - {content.FileName}");
}
DirectoryInfo rootDir = new(Path.Combine(Environment.CurrentDirectory, blogOptions.Root));
if (!rootDir.Exists)
Console.WriteLine($"All {contents.Drafts.Count} Drafts:");
foreach (BlogContent content in contents.Drafts)
{
throw new InvalidOperationException($"Blog source directory '{blogOptions.Root} doesn't exist.");
Console.WriteLine($" - {content.FileName}");
}
if (rootDir.EnumerateFiles().Any(f => f.Name == fileWithExtension))
{
throw new InvalidOperationException($"Target blog '{file}' has been created!");
}
FileInfo newBlogFile = new(Path.Combine(rootDir.FullName, fileWithExtension));
await using StreamWriter newStream = newBlogFile.CreateText();
await newStream.WriteAsync($"""
---
title: {file}
tags:
---
<!--more-->
""");
Console.WriteLine($"Created new blog '{file}.");
}, filenameArgument, new BlogOptionsBinder());
return newCommand;
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder());
}
}

View File

@@ -4,7 +4,6 @@
@using YaeBlog.Core.Models
@inject IEssayContentService Contents
@inject ITableOfContentService TableOfContent
@inject NavigationManager NavigationInstance
<PageTitle>
@@ -129,7 +128,7 @@
NavigationInstance.NavigateTo("/NotFound");
}
_headline = TableOfContent.Headlines[BlogKey];
_headline = Contents.Headlines[BlogKey];
}
private string GenerateSelectorUrl(string selectorId)

View File

@@ -5,5 +5,7 @@ RootCommand rootCommand = new("YaeBlog CLI");
rootCommand.AddServeCommand();
rootCommand.AddNewCommand();
rootCommand.AddListCommand();
rootCommand.AddWatchCommand();
await rootCommand.InvokeAsync(args);

View File

@@ -0,0 +1,6 @@
---
title: test-essay
date: 2024-08-22T22:31:34.3177253+08:00
tags:
---
<!--more-->

Some files were not shown because too many files have changed in this diff Show More