diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 24ad8d8a..e9b17a9f 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -38,18 +38,18 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v7
with:
lfs: true
submodules: recursive
#https://github.com/actions/setup-dotnet/blob/main/README.md
- name: Setup .NET
- uses: actions/setup-dotnet@v5
+ uses: actions/setup-dotnet@v6
with:
- dotnet-version: 10.0.x
- dotnet-quality: 'ga'
- #dotnet-quality: 'preview'
+ dotnet-version: 11.0.x
+ #dotnet-quality: 'ga'
+ dotnet-quality: 'preview'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index acb0d6c3..cba52035 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -13,17 +13,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v7
with:
lfs: true
submodules: recursive
#https://github.com/actions/setup-dotnet/blob/main/README.md
- name: Setup .NET
- uses: actions/setup-dotnet@v5
+ uses: actions/setup-dotnet@v6
with:
- dotnet-version: 10.0.x
- dotnet-quality: 'ga'
- #dotnet-quality: 'preview'
+ dotnet-version: 11.0.x
+ #dotnet-quality: 'ga'
+ dotnet-quality: 'preview'
- name: get render dependencies
run: sh ./download-assets-pipeline.sh
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 00000000..c94c22d9
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,7 @@
+{
+ "recommendations": [
+ "ms-dotnettools.csdevkit",
+ "ms-dotnettools.csharp",
+ "yzhang.markdown-all-in-one"
+ ]
+}
\ No newline at end of file
diff --git a/Assets/BundledAssets/Preview.html b/Assets/BundledAssets/Preview.html
new file mode 100644
index 00000000..593ed36c
--- /dev/null
+++ b/Assets/BundledAssets/Preview.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+ {{Title}}
+
+
+
+
+
+
+
+
+ {{Content}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index d5516913..f85c5d70 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -12,14 +12,10 @@
-
-
-
-
-
-
-
-
+
+
+
+
@@ -33,7 +29,7 @@
-
+
diff --git a/Docs/bookgen.lock b/Docs/bookgen.lock
new file mode 100644
index 00000000..78bcafcb
--- /dev/null
+++ b/Docs/bookgen.lock
@@ -0,0 +1 @@
+12164
\ No newline at end of file
diff --git a/Docs/changelog.md b/Docs/changelog.md
index 9046dd40..6cffaa41 100644
--- a/Docs/changelog.md
+++ b/Docs/changelog.md
@@ -9,16 +9,23 @@ tags: ''
* Breaking: Removed edit command
* Breaking: Removed terminalinstall command, now part of install command
* Breaking: Removed Vstasks command and reworked it into a vscode command
+* New: Based on .NET 11
+* New: New logo by Mariann Kobzi
* New: Build system can be extended with custom build plugins
* New: VSCode command that can generate VSCode tasks.json file for the current project and recomend extensions
* New: Mermaid diagram rendering supported via mmdr
* New: Nomnoml diagram rendering supported via nomnoml
* New: PlantUML diagram rendering supported. Note: Requires seperate PlantUMl download and installation
+* New: convert diagram2svg command to convert nomnoml and mermaid diagrams to SVG format
* New: Search command to search text in bookgen project contents
* New: Support for openCli specification
* New: Mixcloud media embeding supported
* New: Html2OpenXml command to convert HTML to OpenXML format for Word processing
* New: Template engine reworked, now supports functions and is more robust
+* New: Preview command to preview the current project files in a browser
+* New: table-csv syntax highlighting, that renders the data as a table in HTML output
+* New: table-tsv syntax highlighting, that renders the data as a table in HTML output
+* New: table-ssv syntax highlighting, that renders the data as a table in HTML output
* Change: Commands documentation generated from code and is now more complete
* Change: App settings reworked
* Change: Dropped mathjax as dependency for formula rendering. New formula rendering is based on katex and works with markdown syntax
diff --git a/Docs/manual.md b/Docs/manual.md
index 344ef518..397aed46 100644
--- a/Docs/manual.md
+++ b/Docs/manual.md
@@ -198,6 +198,9 @@ Markdown files are the main input format for BookGen. BookGen supports the follo
* YAML front matter for metadata
* Table of contents via the `[toc]` or `[toc maxlevel=n]` tag, where n is the level of headdings to include
* Keyboard input via the `<<` and `>>` tags, which will render the text between them as keyboard input.
+* Comma separated values tables via the `table-csv` language identifier for source code. It only works for comma separated values.
+* Tab separated values tables via the `table-tsv` language identifier for source code. It only works for tab separated values.
+* Semicolon separated values tables via the `table-ssv` language identifier for source code. It only works for semicolon separated values.
### YAML Front matter
diff --git a/Source/BookGen.Api/V1/RenderTags.cs b/Source/BookGen.Api/V1/RenderTags.cs
index eed196f4..2178af06 100644
--- a/Source/BookGen.Api/V1/RenderTags.cs
+++ b/Source/BookGen.Api/V1/RenderTags.cs
@@ -33,5 +33,5 @@ public sealed class RenderTags
///
/// Additional data that can be used in the rendering process.
///
- public Dictionary AdditionalData { get; set; } = new();
+ public Dictionary AdditionalData { get; set; } = [];
}
diff --git a/Source/BookGen.Cli/BookGen.Cli.csproj b/Source/BookGen.Cli/BookGen.Cli.csproj
index d9942b02..a5f01b26 100644
--- a/Source/BookGen.Cli/BookGen.Cli.csproj
+++ b/Source/BookGen.Cli/BookGen.Cli.csproj
@@ -1,6 +1,6 @@
- net10.0
+ net11.0
enable
enable
..\..\bin\$(Configuration)\
@@ -10,8 +10,6 @@
-
-
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Source/BookGen.Cli/packages.lock.json b/Source/BookGen.Cli/packages.lock.json
index 6510ee4d..9a7eb3f9 100644
--- a/Source/BookGen.Cli/packages.lock.json
+++ b/Source/BookGen.Cli/packages.lock.json
@@ -1,22 +1,7 @@
{
"version": 2,
"dependencies": {
- "net10.0": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": {
- "type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "z/2xXlFw2aLGjHyEm6E0tQ+In6VfzQzTrtArbQ2c0TQE16ZbyDCMGPvaUT9I0s8rgy9sRWlU2P9waW37qV04qA=="
- },
- "Microsoft.Extensions.Logging.Abstractions": {
- "type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "zkFxGYUvdxAvIKTyXHrmW+Sux53D4SezD9dMyZ6hrwwzPQJNuwCRy1f5W7AvYTqacEGhWF2XderRQG1OvbV8og==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10"
- }
- },
+ "net11.0": {
"Roslynator.Analyzers": {
"type": "Direct",
"requested": "[4.15.0, )",
diff --git a/Source/BookGen.Contents/BookGen.Contents.csproj b/Source/BookGen.Contents/BookGen.Contents.csproj
index 76db8ef6..d07e6253 100644
--- a/Source/BookGen.Contents/BookGen.Contents.csproj
+++ b/Source/BookGen.Contents/BookGen.Contents.csproj
@@ -1,6 +1,6 @@
- net10.0
+ net11.0
enable
enable
..\..\bin\$(Configuration)\
diff --git a/Source/BookGen.Lib/BookGen.Lib.csproj b/Source/BookGen.Lib/BookGen.Lib.csproj
index 513faa47..3a777f7e 100644
--- a/Source/BookGen.Lib/BookGen.Lib.csproj
+++ b/Source/BookGen.Lib/BookGen.Lib.csproj
@@ -1,7 +1,7 @@
- net10.0
+ net11.0
enable
enable
preview
diff --git a/Source/BookGen.Lib/BundledAssets.cs b/Source/BookGen.Lib/BundledAssets.cs
index 0ba9fac3..fefd95ce 100644
--- a/Source/BookGen.Lib/BundledAssets.cs
+++ b/Source/BookGen.Lib/BundledAssets.cs
@@ -15,6 +15,7 @@ public static class BundledAssets
public const string TemplatePrint = "Print.html";
public const string TemplateEpub = "Epub.html";
public const string TemplateBlank = "Blank.html";
+ public const string TemplatePreview = "Preview.html";
public const string QrCodeJs = "qrcode.min.js";
public const string JsPageToc = "PageToc.js";
public const string NomnomlJs = "nomnoml.js";
diff --git a/Source/BookGen.Lib/DisposableTracker.cs b/Source/BookGen.Lib/DisposableTracker.cs
new file mode 100644
index 00000000..49983bb3
--- /dev/null
+++ b/Source/BookGen.Lib/DisposableTracker.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+namespace BookGen.Lib;
+
+internal sealed class DisposableTracker : IDisposable
+{
+ private readonly List _items = new();
+ private readonly Lock _lock = new();
+
+ public void Dispose()
+ {
+ lock (_lock)
+ {
+ foreach (IDisposable item in _items)
+ {
+ item.Dispose();
+ }
+ _items.Clear();
+ }
+ }
+
+ public void Track(T item) where T : IDisposable
+ {
+ lock (_lock)
+ {
+ _items.Add(item);
+ }
+ }
+}
diff --git a/Source/BookGen.Lib/Http/ErrorPageTemplate.html b/Source/BookGen.Lib/Http/ErrorPageTemplate.html
index 329ded85..edb073a9 100644
--- a/Source/BookGen.Lib/Http/ErrorPageTemplate.html
+++ b/Source/BookGen.Lib/Http/ErrorPageTemplate.html
@@ -157,13 +157,13 @@
diff --git a/Source/BookGen.Lib/Http/HttpServer.cs b/Source/BookGen.Lib/Http/HttpServer.cs
index fa7977c6..e9a4d224 100644
--- a/Source/BookGen.Lib/Http/HttpServer.cs
+++ b/Source/BookGen.Lib/Http/HttpServer.cs
@@ -10,27 +10,40 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Rewrite;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
+using BookGen.Lib;
+
namespace BookGen.Lib.Http;
internal sealed class HttpServer : IHttpServer
{
private readonly WebApplication _app;
+ private readonly bool _localhostOnly;
+ private readonly DisposableTracker _disposableTracker;
public int Port { get; }
- public HttpServer(int port, ILogger logger)
+ public HttpServer(int port, ILogger logger, bool localHostOnly)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
#pragma warning disable CA2000 // Dispose objects before losing scope
builder.Logging.AddProvider(new LoggerProvider(logger));
#pragma warning restore CA2000 // Dispose objects before losing scope
- builder.WebHost.ConfigureKestrel((context, serverOptions) => serverOptions.ListenAnyIP(port));
+
+ if (localHostOnly)
+ {
+ builder.WebHost.ConfigureKestrel((context, serverOptions) => serverOptions.ListenLocalhost(port));
+ }
+ else
+ {
+ builder.WebHost.ConfigureKestrel((context, serverOptions) => serverOptions.ListenAnyIP(port));
+ }
+ _localhostOnly = localHostOnly;
+ _disposableTracker = new DisposableTracker();
_app = builder.Build();
Port = port;
@@ -62,6 +75,12 @@ public HttpServer(int port, ILogger logger)
});
}
+ public ValueTask DisposeAsync()
+ {
+ _disposableTracker.Dispose();
+ return _app.DisposeAsync();
+ }
+
public void AddStaticFiles(string directory, string requestPath, bool directoryBrowseEnabled)
{
var options = new FileServerOptions
@@ -123,7 +142,7 @@ public void AddRoute(ApiMetaData metaData, RequestDelegate handler)
}
}
- public void AddRoutes(IReadOnlyDictionary routes)
+ public void AddRoutes(IEnumerable> routes)
{
foreach (KeyValuePair route in routes)
{
@@ -131,8 +150,26 @@ public void AddRoutes(IReadOnlyDictionary routes)
}
}
+ public void AddRoutes(IRouteProvider provider)
+ {
+ if (provider is IDisposable disposable)
+ {
+ _disposableTracker.Track(disposable);
+ }
+ foreach ((ApiMetaData metaData, RequestDelegate handler) in provider.Routes)
+ {
+ AddRoute(metaData, handler);
+ }
+ }
+
public IEnumerable GetListenUrls()
{
+ if (_localhostOnly)
+ {
+ yield return $"http://localhost:{Port}";
+ yield break;
+ }
+
foreach ((IPAddress? adress, IPAddress _) in GetIpAdresses())
{
yield return $"http://{adress}:{Port}";
@@ -161,7 +198,4 @@ public async Task StartAsync()
public async Task StopAsync()
=> await _app.StopAsync();
-
- public ValueTask DisposeAsync()
- => _app.DisposeAsync();
}
diff --git a/Source/BookGen.Lib/Http/ServerFactory.cs b/Source/BookGen.Lib/Http/HttpServerFactory.cs
similarity index 70%
rename from Source/BookGen.Lib/Http/ServerFactory.cs
rename to Source/BookGen.Lib/Http/HttpServerFactory.cs
index ca0fe4cc..b30fb53b 100644
--- a/Source/BookGen.Lib/Http/ServerFactory.cs
+++ b/Source/BookGen.Lib/Http/HttpServerFactory.cs
@@ -6,16 +6,20 @@
using System.Net.Mime;
using System.Net.NetworkInformation;
+using BookGen.Lib.AppSettings;
+using BookGen.Vfs;
+
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace BookGen.Lib.Http;
-public static class ServerFactory
+public static class HttpServerFactory
{
public const int HostingPort = 8081;
+ public const int PreviewPort = 8181;
- private static int ChoosePort(int @default = HostingPort)
+ private static int ChoosePort(int @default)
{
IPGlobalProperties ipProps = IPGlobalProperties.GetIPGlobalProperties();
@@ -52,7 +56,7 @@ private static int ChoosePort(int @default = HostingPort)
public static IHttpServer CreateServerForDirectoryHosting(string directoryToServe, ILogger logger)
{
- var server = new HttpServer(ChoosePort(), logger);
+ var server = new HttpServer(ChoosePort(HostingPort), logger, localHostOnly: false);
server.AddStaticFiles(directory: directoryToServe, requestPath: "", directoryBrowseEnabled: true);
server.AddRoute(new ApiMetaData("/qrcodelink", MediaTypeNames.Text.Html), async context =>
{
@@ -62,4 +66,15 @@ public static IHttpServer CreateServerForDirectoryHosting(string directoryToServ
});
return server;
}
+
+ public static IHttpServer CreateServerForPreview(IReadOnlyFileSystem source,
+ ILogger logger,
+ IProgramPathResolver programPathResolver,
+ IAssetSource assetSource)
+ {
+ var server = new HttpServer(ChoosePort(PreviewPort), logger, localHostOnly: true);
+ var previewRoutes = new PreviewRoutes(source, logger, programPathResolver, assetSource);
+ server.AddRoutes(previewRoutes);
+ return server;
+ }
}
diff --git a/Source/BookGen.Lib/Http/IRouteProvider.cs b/Source/BookGen.Lib/Http/IRouteProvider.cs
new file mode 100644
index 00000000..81ecb985
--- /dev/null
+++ b/Source/BookGen.Lib/Http/IRouteProvider.cs
@@ -0,0 +1,13 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+using Microsoft.AspNetCore.Http;
+
+namespace BookGen.Lib.Http;
+
+internal interface IRouteProvider
+{
+ IEnumerable<(ApiMetaData metaData, RequestDelegate handler)> Routes { get; }
+}
diff --git a/Source/BookGen.Lib/Http/MimeTypes.cs b/Source/BookGen.Lib/Http/MimeTypes.cs
index 8143a5bf..7cab2c01 100644
--- a/Source/BookGen.Lib/Http/MimeTypes.cs
+++ b/Source/BookGen.Lib/Http/MimeTypes.cs
@@ -3,10 +3,6 @@
// This code is licensed under MIT license (see LICENSE for details)
//-----------------------------------------------------------------------------
-using System.Diagnostics.CodeAnalysis;
-
-using Microsoft.AspNetCore.StaticFiles;
-
namespace BookGen.Lib.Http;
///
diff --git a/Source/BookGen.Lib/Http/PageFactory.cs b/Source/BookGen.Lib/Http/PageFactory.cs
index 10f408cd..3e21b102 100644
--- a/Source/BookGen.Lib/Http/PageFactory.cs
+++ b/Source/BookGen.Lib/Http/PageFactory.cs
@@ -22,10 +22,27 @@ private static string GetResource(string resoruceName)
return content;
}
+ public static string GetFiles(List allowedFiles)
+ {
+ var files = new StringBuilder();
+ IEnumerable> groups = allowedFiles.GroupBy(f => Path.GetDirectoryName(f) ?? string.Empty);
+ foreach (IGrouping group in groups)
+ {
+ files.AppendLine($"{HttpUtility.HtmlEncode(group.Key)}
");
+ files.AppendLine("");
+ }
+ return files.ToString();
+ }
+
public static string GetErrorPage(int code, string message)
{
var page = GetResource($"BookGen.Lib.Http.ErrorPageTemplate.html");
- return page.Replace("{{code}}", code.ToString()).Replace("{{message}}", message);
+ return page.Replace("{{Code}}", code.ToString()).Replace("{{Message}}", message);
}
public static string GetQrCodePage(IEnumerable urls)
@@ -39,6 +56,6 @@ public static string GetQrCodePage(IEnumerable urls)
qrcodes.AppendLine("");
}
- return GetResource("BookGen.Lib.Http.QRCodeTemplate.html").Replace("{{links}}", qrcodes.ToString());
+ return GetResource("BookGen.Lib.Http.QRCodeTemplate.html").Replace("{{Links}}", qrcodes.ToString());
}
}
diff --git a/Source/BookGen.Lib/Http/PreviewRoutes.cs b/Source/BookGen.Lib/Http/PreviewRoutes.cs
new file mode 100644
index 00000000..6defef68
--- /dev/null
+++ b/Source/BookGen.Lib/Http/PreviewRoutes.cs
@@ -0,0 +1,199 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+using System.Diagnostics.CodeAnalysis;
+using System.Net;
+using System.Net.Mime;
+
+using BookGen.Lib.AppSettings;
+using BookGen.Lib.Domain;
+using BookGen.Lib.Domain.IO.Configuration;
+using BookGen.Lib.Rendering;
+using BookGen.Lib.Rendering.Images;
+using BookGen.Lib.Rendering.Markdown;
+using BookGen.Lib.Rendering.Markdown.RenderInterop;
+using BookGen.Lib.Rendering.Templates;
+using BookGen.Vfs;
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace BookGen.Lib.Http;
+
+internal sealed class PreviewRoutes : IDisposable, IRouteProvider
+{
+ private readonly IReadOnlyFileSystem _source;
+ private readonly ILogger _logger;
+ private readonly MarkdownRenderSettings _renderSettings;
+ private readonly MarkdownConverter _markdownConverter;
+ private readonly TemplateEngine _templateEngine;
+ private readonly string _template;
+
+ private readonly IFileSystemObserver _observer;
+ private readonly List _allowedFiles;
+ private readonly Lock _lock;
+
+ public PreviewRoutes(IReadOnlyFileSystem source,
+ ILogger logger,
+ IProgramPathResolver programPathResolver,
+ IAssetSource assetSource)
+ {
+ _allowedFiles = new List();
+ _lock = new Lock();
+ _source = source;
+ _logger = logger;
+ var imageConfig = new ImageConfig
+ {
+ SvgRecode = SvgRecodeOption.Passtrough,
+ ResizeAndRecodeImages = ImgRecodeOption.Passtrough,
+ };
+
+ var imgService = new ImgService(source, _logger, imageConfig);
+
+ _renderSettings = new MarkdownRenderSettings(imgService)
+ {
+ HostUrl = "http://localhost",
+ DeleteFirstH1 = false,
+ CssClasses = new CssClasses(),
+ OffsetHeadingsBy = 0,
+ AutoEmbedSupportedLinks = true,
+ RenderInterop = new RenderInterop(assetSource, programPathResolver, imageConfig)
+ };
+
+ _markdownConverter = new MarkdownConverter(_renderSettings);
+ _templateEngine = new TemplateEngine(_logger, assetSource);
+ _template = assetSource.GetAsset(BundledAssets.TemplatePreview);
+
+ _observer = _source.CreateObserver(logger, "*.md");
+ _observer.FileChanged += OnFileChange;
+ _allowedFiles.Clear();
+
+ var files = _source.GetFiles(_source.Scope, "*.md", true)
+ .Select(f => Path.GetRelativePath(_source.Scope, f));
+
+ _allowedFiles.AddRange(files);
+ }
+
+ private void OnFileChange(object? sender, FileSystemChangeEventArgs e)
+ {
+ lock (_lock)
+ {
+ switch (e.ChangeType)
+ {
+ case FileSystemChangeEventArgs.Change.Created:
+ _allowedFiles.Add(Path.GetRelativePath(_source.Scope, e.FileName));
+ break;
+ case FileSystemChangeEventArgs.Change.Changed:
+ break;
+ case FileSystemChangeEventArgs.Change.Renamed:
+ int oldindex = _allowedFiles.IndexOf(Path.GetRelativePath(_source.Scope, e.FileName));
+ if (oldindex != -1 && !string.IsNullOrEmpty(e.NewFileName))
+ {
+ _allowedFiles[oldindex] = Path.GetRelativePath(_source.Scope, e.NewFileName);
+ }
+ else if (!string.IsNullOrEmpty(e.NewFileName))
+ {
+ _allowedFiles.Add(Path.GetRelativePath(_source.Scope, e.NewFileName));
+ }
+ break;
+ case FileSystemChangeEventArgs.Change.Deleted:
+ _allowedFiles.Remove(Path.GetRelativePath(_source.Scope, e.FileName));
+ break;
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _observer.FileChanged -= OnFileChange;
+ _observer.Dispose();
+ _markdownConverter.Dispose();
+ _renderSettings.Dispose();
+ }
+
+ public IEnumerable<(ApiMetaData metaData, RequestDelegate handler)> Routes
+ {
+ get
+ {
+ yield return (new ApiMetaData("/preview", MediaTypeNames.Text.Html, ApiMethod.Get), RenderPreview);
+ yield return (new ApiMetaData("/", MediaTypeNames.Text.Html, ApiMethod.Get), RenderIndex);
+ }
+ }
+
+ private async Task RenderIndex(HttpContext context)
+ {
+ string content;
+ lock (_lock)
+ {
+ content = PageFactory.GetFiles(_allowedFiles);
+ }
+
+ var viewData = new ViewData
+ {
+ Host = "/",
+ Content = content,
+ Title = "Previewable files",
+ LastModified = DateTime.UtcNow,
+ };
+
+ await SendData(context,
+ HttpStatusCode.OK,
+ _templateEngine.Render(_template, viewData),
+ MediaTypeNames.Text.Html);
+ }
+
+ private async Task RenderPreview(HttpContext context)
+ {
+ string? fileName = context.Request.Query["file"];
+ if (!CanServe(fileName))
+ {
+ var viewData = new ViewData
+ {
+ Host = "/",
+ Content = "Not a markdown file",
+ Title = "Error",
+ LastModified = DateTime.UtcNow,
+ };
+ await SendData(context,
+ HttpStatusCode.BadRequest,
+ _templateEngine.Render(_template, viewData),
+ MediaTypeNames.Text.Html);
+
+ return;
+ }
+
+ SourceFile source = await _source.GetSourceFile(fileName, _logger);
+
+ var data = new ViewData
+ {
+ Content = _markdownConverter.RenderMarkdownToHtml(source.Content),
+ Title = source.FrontMatter.Title,
+ LastModified = source.LastModified,
+ Host = "/"
+ };
+
+ await SendData(context,
+ HttpStatusCode.OK,
+ _templateEngine.Render(_template, data),
+ MediaTypeNames.Text.Html);
+
+ }
+
+ private bool CanServe([NotNullWhen(true)] string? fileName)
+ {
+ lock (_lock)
+ {
+ return !string.IsNullOrEmpty(fileName)
+ && _allowedFiles.Contains(fileName);
+ }
+ }
+
+ private static async Task SendData(HttpContext httpContext, HttpStatusCode httpStatusCode, string data, string mimeType)
+ {
+ httpContext.Response.StatusCode = (int)httpStatusCode;
+ httpContext.Response.ContentType = mimeType;
+ await httpContext.Response.WriteAsync(data);
+ }
+}
diff --git a/Source/BookGen.Lib/Http/QRCodeTemplate.html b/Source/BookGen.Lib/Http/QRCodeTemplate.html
index 557f1a39..4f3f63ac 100644
--- a/Source/BookGen.Lib/Http/QRCodeTemplate.html
+++ b/Source/BookGen.Lib/Http/QRCodeTemplate.html
@@ -162,8 +162,8 @@
-
Liks for this server
- {{links}}
+ Links for this server
+ {{Links}}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SvgMathBlockRenderer.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SvgMathBlockRenderer.cs
index 21777471..a854a5b2 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SvgMathBlockRenderer.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SvgMathBlockRenderer.cs
@@ -26,7 +26,7 @@ protected override void Write(HtmlRenderer renderer, MathBlock obj)
renderer.EnsureLine();
renderer.Write("");
string code = obj.GetCode();
- renderer.Write(_latexRenderPlugin.Render(code));
+ renderer.Write(_latexRenderPlugin.Render(code, ""));
renderer.WriteLine("
");
}
}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SvgMathInlineRenderer.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SvgMathInlineRenderer.cs
index 45986321..e9f47480 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SvgMathInlineRenderer.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SvgMathInlineRenderer.cs
@@ -31,7 +31,7 @@ protected override void Write(HtmlRenderer renderer, MathInline obj)
}
renderer.Write("");
- renderer.Write(_latexRenderPlugin.Render(obj.Content.Text));
+ renderer.Write(_latexRenderPlugin.Render(obj.Content.Text, ""));
renderer.Write("");
}
}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/CsvRenderPlugin.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/CsvRenderPlugin.cs
new file mode 100644
index 00000000..1a40225f
--- /dev/null
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/CsvRenderPlugin.cs
@@ -0,0 +1,113 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+using System.Diagnostics;
+using System.Text;
+using System.Web;
+
+namespace BookGen.Lib.Rendering.Markdown.Renderers.SyntaxRenderPlugins;
+
+internal sealed class CsvRenderPlugin : SyntaxRendererPlugin
+{
+ private const string ComaSeperated = "table-csv";
+ private const string TabSeperated = "table-tsv";
+ private const string SemicolonSeperated = "table-ssv";
+
+ public override string[] LanguageMonikers { get; }
+ = [ComaSeperated, TabSeperated, SemicolonSeperated];
+
+ public override string Render(string code, string parsedLanguageMoniker)
+ {
+ var rendered = new StringBuilder(code.Length);
+ char delimiter = GetDelimiter(parsedLanguageMoniker);
+ using var parser = new CsvParser(delimiter, code);
+ rendered.AppendLine("");
+ foreach (List record in parser.GetRecords())
+ {
+ rendered.AppendLine("");
+ foreach (string field in record)
+ {
+ rendered.AppendLine($"| {HttpUtility.HtmlEncode(field)} | ");
+ }
+ rendered.AppendLine("
");
+ }
+ rendered.AppendLine("
");
+ return rendered.ToString();
+ }
+
+ private static char GetDelimiter(string parsedLanguageMoniker)
+ {
+ return parsedLanguageMoniker switch
+ {
+ ComaSeperated => ',',
+ TabSeperated => '\t',
+ SemicolonSeperated => ';',
+ _ => throw new UnreachableException($"Unknown language moniker: {parsedLanguageMoniker}"),
+ };
+ }
+
+ internal sealed class CsvParser : IDisposable
+ {
+ private readonly StringReader _reader;
+ private readonly char _delimiter;
+
+ public CsvParser(char delimiter, string input)
+ {
+ _reader = new StringReader(input);
+ _delimiter = delimiter;
+ }
+
+ public void Dispose()
+ {
+ _reader.Dispose();
+ }
+
+ public IEnumerable> GetRecords()
+ {
+ string? line;
+ while ((line = _reader.ReadLine()) != null)
+ {
+ if (!string.IsNullOrWhiteSpace(line))
+ yield return ParseLine(line);
+ }
+ }
+
+ private List ParseLine(string line)
+ {
+ var result = new List();
+ var current = new StringBuilder();
+ bool inQuotes = false;
+
+ for (int i = 0; i < line.Length; i++)
+ {
+ char c = line[i];
+ if (c == '"')
+ {
+ if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
+ {
+ current.Append('"');
+ i++;
+ }
+ else
+ {
+ inQuotes = !inQuotes;
+ }
+ }
+ else if (c == _delimiter && !inQuotes)
+ {
+ result.Add(current.ToString());
+ current.Clear();
+ }
+ else
+ {
+ current.Append(c);
+ }
+ }
+
+ result.Add(current.ToString());
+ return result;
+ }
+ }
+}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/LatexRenderPlugin.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/LatexRenderPlugin.cs
index 38f680ac..4954ef73 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/LatexRenderPlugin.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/LatexRenderPlugin.cs
@@ -9,9 +9,9 @@ namespace BookGen.Lib.Rendering.Markdown.Renderers.SyntaxRenderPlugins;
internal sealed class LatexRenderPlugin(IRenderInterop renderInterop) : SyntaxRendererPlugin
{
- public override string LanguageMoniker { get; } = "latex";
+ public override string[] LanguageMonikers { get; } = ["latex"];
- public override string Render(string code)
+ public override string Render(string code, string parsedLanguageMoniker)
{
return RendererImgage(renderInterop.RenderLatex(code));
}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/MermaidRenderPlugin.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/MermaidRenderPlugin.cs
index 680fd04a..ad834cfd 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/MermaidRenderPlugin.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/MermaidRenderPlugin.cs
@@ -9,9 +9,9 @@ namespace BookGen.Lib.Rendering.Markdown.Renderers.SyntaxRenderPlugins;
internal sealed class MermaidRenderPlugin(IRenderInterop renderInterop) : SyntaxRendererPlugin
{
- public override string LanguageMoniker { get; } = "mermaid";
+ public override string[] LanguageMonikers { get; } = ["mermaid"];
- public override string Render(string code)
+ public override string Render(string code, string parsedLanguageMoniker)
{
return RendererImgage(renderInterop.RenderMermaid(code));
}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/NomnomlRenderPlugin.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/NomnomlRenderPlugin.cs
index 6f524cdf..61bdf0de 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/NomnomlRenderPlugin.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/NomnomlRenderPlugin.cs
@@ -9,9 +9,9 @@ namespace BookGen.Lib.Rendering.Markdown.Renderers.SyntaxRenderPlugins;
internal sealed class NomnomlRenderPlugin(IRenderInterop renderInterop) : SyntaxRendererPlugin
{
- public override string LanguageMoniker { get; } = "nomnoml";
+ public override string[] LanguageMonikers { get; } = ["nomnoml"];
- public override string Render(string code)
+ public override string Render(string code, string parsedLanguageMoniker)
{
return RendererImgage(renderInterop.RenderNomnoml(code));
}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/PlantUmlRenderPlugin.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/PlantUmlRenderPlugin.cs
index 7291fc33..de8401ba 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/PlantUmlRenderPlugin.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/PlantUmlRenderPlugin.cs
@@ -9,9 +9,9 @@ namespace BookGen.Lib.Rendering.Markdown.Renderers.SyntaxRenderPlugins;
internal sealed class PlantUmlRenderPlugin(IRenderInterop renderInterop) : SyntaxRendererPlugin
{
- public override string LanguageMoniker => "plantuml";
+ public override string[] LanguageMonikers { get; } = ["plantuml"];
- public override string Render(string code)
+ public override string Render(string code, string parsedLanguageMoniker)
{
return RendererImgage(renderInterop.RenderPlantUml(code));
}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/QrCodeRenderPlugin.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/QrCodeRenderPlugin.cs
index 3ff291d6..805686a9 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/QrCodeRenderPlugin.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/QrCodeRenderPlugin.cs
@@ -9,9 +9,9 @@ namespace BookGen.Lib.Rendering.Markdown.Renderers.SyntaxRenderPlugins;
internal sealed class QrCodeRenderPlugin(IRenderInterop renderInterop) : SyntaxRendererPlugin
{
- public override string LanguageMoniker { get; } = "qrcode";
+ public override string[] LanguageMonikers { get; } = ["qrcode"];
- public override string Render(string code)
+ public override string Render(string code, string parsedLanguageMoniker)
{
return RendererImgage(renderInterop.RenderQrCode(code));
}
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/SyntaxRendererPlugin.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/SyntaxRendererPlugin.cs
index a4134219..55c2838c 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/SyntaxRendererPlugin.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/SyntaxRendererPlugin.cs
@@ -9,9 +9,9 @@ namespace BookGen.Lib.Rendering.Markdown.Renderers.SyntaxRenderPlugins;
internal abstract class SyntaxRendererPlugin
{
- public abstract string LanguageMoniker { get; }
+ public abstract string[] LanguageMonikers { get; }
- public abstract string Render(string code);
+ public abstract string Render(string code, string parsedLanguageMoniker);
protected static string RendererImgage(ImageResult img)
{
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/TerminalRenderPlugin.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/TerminalRenderPlugin.cs
index e885ec12..87d2400e 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/TerminalRenderPlugin.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderPlugins/TerminalRenderPlugin.cs
@@ -18,9 +18,9 @@ internal sealed class TerminalRenderPlugin : SyntaxRendererPlugin
""";
- public override string LanguageMoniker { get; } = "terminal";
+ public override string[] LanguageMonikers { get; } = ["terminal"];
- public override string Render(string code)
+ public override string Render(string code, string parsedLanguageMoniker)
{
const string codeTag = "";
return TerminalHtml.Replace(codeTag, TerminalRenderer.RenderAnsiCode(HttpUtility.HtmlEncode(code)));
diff --git a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderer.cs b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderer.cs
index 869305ab..55c46b36 100644
--- a/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderer.cs
+++ b/Source/BookGen.Lib/Rendering/Markdown/Renderers/SyntaxRenderer.cs
@@ -60,12 +60,16 @@ public SyntaxRenderer(CodeBlockRenderer underlyingRenderer,
RegisterPlugin(new QrCodeRenderPlugin(_renderInterop));
RegisterPlugin(new MermaidRenderPlugin(_renderInterop));
RegisterPlugin(new PlantUmlRenderPlugin(_renderInterop));
+ RegisterPlugin(new CsvRenderPlugin());
}
private void RegisterPlugin(SyntaxRendererPlugin plugin)
{
- _plugins[plugin.LanguageMoniker] = plugin;
- _supportedLanguages.Add(plugin.LanguageMoniker);
+ foreach (var languageMoniker in plugin.LanguageMonikers.Distinct())
+ {
+ _plugins[languageMoniker] = plugin;
+ _supportedLanguages.Add(languageMoniker);
+ }
}
protected override void Write(HtmlRenderer renderer, CodeBlock obj)
@@ -93,7 +97,7 @@ protected override void Write(HtmlRenderer renderer, CodeBlock obj)
if (_plugins.TryGetValue(languageMoniker, out SyntaxRendererPlugin? plugin))
{
- renderer.Write(plugin.Render(code));
+ renderer.Write(plugin.Render(code, languageMoniker));
return;
}
diff --git a/Source/BookGen.Lib/packages.lock.json b/Source/BookGen.Lib/packages.lock.json
index 802ed806..b6b6ae91 100644
--- a/Source/BookGen.Lib/packages.lock.json
+++ b/Source/BookGen.Lib/packages.lock.json
@@ -1,7 +1,7 @@
{
"version": 2,
"dependencies": {
- "net10.0": {
+ "net11.0": {
"AngleSharp": {
"type": "Direct",
"requested": "[1.5.2, )",
@@ -95,9 +95,9 @@
},
"System.ServiceModel.Syndication": {
"type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "jU3WQG477Tk7RYlM2Ky8oySb6WHd744LgK7MYRMe4L8FcoJI3Gbpcf29sB2lIlJVS82ah9i4FUw3RfbXeJs8jw=="
+ "requested": "[11.0.0-preview.6.26359.118, )",
+ "resolved": "11.0.0-preview.6.26359.118",
+ "contentHash": "StArv4DSO8I8QjDo1n+FzKeaq6UTNKvgkHHFaAt8ueexUWJZSEZ5/EasqwKt7413n3carQjaFzSULDI7sxwg0A=="
},
"YamlDotNet": {
"type": "Direct",
diff --git a/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj b/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj
index d8d56396..5f5685fd 100644
--- a/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj
+++ b/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj
@@ -1,7 +1,7 @@
- net10.0
+ net11.0
enable
enable
true
@@ -9,7 +9,6 @@
-
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json
index f96ef418..77d5e1f3 100644
--- a/Source/BookGen.Shell.Shared/packages.lock.json
+++ b/Source/BookGen.Shell.Shared/packages.lock.json
@@ -1,16 +1,7 @@
{
"version": 2,
"dependencies": {
- "net10.0": {
- "Microsoft.Extensions.Logging.Abstractions": {
- "type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "zkFxGYUvdxAvIKTyXHrmW+Sux53D4SezD9dMyZ6hrwwzPQJNuwCRy1f5W7AvYTqacEGhWF2XderRQG1OvbV8og==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10"
- }
- },
+ "net11.0": {
"Roslynator.Analyzers": {
"type": "Direct",
"requested": "[4.15.0, )",
@@ -42,12 +33,6 @@
"type": "Transitive",
"resolved": "0.57.2",
"contentHash": "Y1+u73shwP+JYHmrkdN6bSt2kaGBEAafhi2nrecczbXdFRey+k7J/WF9YPfcB0rqivv2S8fmif6FSBq9m/AFvw=="
- },
- "Microsoft.Extensions.DependencyInjection.Abstractions": {
- "type": "CentralTransitive",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "z/2xXlFw2aLGjHyEm6E0tQ+In6VfzQzTrtArbQ2c0TQE16ZbyDCMGPvaUT9I0s8rgy9sRWlU2P9waW37qV04qA=="
}
}
}
diff --git a/Source/BookGen.Shellprog/BookGen.Shellprog.csproj b/Source/BookGen.Shellprog/BookGen.Shellprog.csproj
index 2ccdc818..00796fc0 100644
--- a/Source/BookGen.Shellprog/BookGen.Shellprog.csproj
+++ b/Source/BookGen.Shellprog/BookGen.Shellprog.csproj
@@ -2,7 +2,7 @@
Exe
- net10.0
+ net11.0
enable
enable
..\..\bin\$(Configuration)\
@@ -14,9 +14,7 @@
-
-
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json
index 402e9f33..e31f7120 100644
--- a/Source/BookGen.Shellprog/packages.lock.json
+++ b/Source/BookGen.Shellprog/packages.lock.json
@@ -1,40 +1,20 @@
{
"version": 2,
"dependencies": {
- "net10.0": {
+ "net11.0": {
"Microsoft.Extensions.DependencyInjection": {
"type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "ANyvsgkNBRvcJh2XLgn8veGmajf+8m0AbKK+HPWdRL1yraSNVVSmQhFntLtdz/C795jxqqup+k05cs/3jZQPOA==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10"
- }
- },
- "Microsoft.Extensions.DependencyInjection.Abstractions": {
- "type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "z/2xXlFw2aLGjHyEm6E0tQ+In6VfzQzTrtArbQ2c0TQE16ZbyDCMGPvaUT9I0s8rgy9sRWlU2P9waW37qV04qA=="
+ "requested": "[11.0.0-preview.6.26359.118, )",
+ "resolved": "11.0.0-preview.6.26359.118",
+ "contentHash": "MwlfMwxXewuq+X85a31yYx6emRYIyJOboVXZtRH3Wvp/hZZWmlg1pu/tI+XJUm4ltoCNMzpcFF6zJcmbnbnuGA=="
},
"Microsoft.Extensions.Logging": {
"type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "Tf6z5HsL0VDYRTfvsoNrTGHGheCwkTsZBA2FFh5ATJUbkAwug+FFNISJK2gjpUNemlAOoWllAK52HOWCjto3EQ==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection": "10.0.10",
- "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
- "Microsoft.Extensions.Options": "10.0.10"
- }
- },
- "Microsoft.Extensions.Logging.Abstractions": {
- "type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "zkFxGYUvdxAvIKTyXHrmW+Sux53D4SezD9dMyZ6hrwwzPQJNuwCRy1f5W7AvYTqacEGhWF2XderRQG1OvbV8og==",
+ "requested": "[11.0.0-preview.6.26359.118, )",
+ "resolved": "11.0.0-preview.6.26359.118",
+ "contentHash": "lUAZ402asMsLYe483nXXQIphklpTNDP6LmhmrwB8V3gBTjXeUp40N6h/Ap5QOiGHqsAiooznL4lBETGSncYsxQ==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10"
+ "Microsoft.Extensions.DependencyInjection": "11.0.0-preview.6.26359.118"
}
},
"Roslynator.Analyzers": {
@@ -58,20 +38,6 @@
"resolved": "1.0.0",
"contentHash": "TojbwWASRf3PMUsotXa8MQ9SNwEwWZZ5bAYf1eLv+jiDsxaD/n7TKTi3da6z4+m8uZHri2i+BVRuLj5w+6j64Q=="
},
- "Microsoft.Extensions.Options": {
- "type": "Transitive",
- "resolved": "10.0.10",
- "contentHash": "srnhnk7nE8krBiIXp71LvBmKBtraBONWSRzdjJgRv1Ko9Mp8IVNqv4vIS9hGeVteBig8aQkva9ZG+sC+o5sVcA==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
- "Microsoft.Extensions.Primitives": "10.0.10"
- }
- },
- "Microsoft.Extensions.Primitives": {
- "type": "Transitive",
- "resolved": "10.0.10",
- "contentHash": "5wu/GrYVd8mG2DVUw3vFJzF+O336TyTGg/Kmcgw9bfwYhCoFiV5lR5QeEmKecJyrW4W54nMfD3p3589E8a7czQ=="
- },
"Spectre.Console.Ansi": {
"type": "Transitive",
"resolved": "0.57.2",
@@ -80,9 +46,7 @@
"bookgen.cli": {
"type": "Project",
"dependencies": {
- "BookGen.Vfs": "[1.0.0, )",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.10, )",
- "Microsoft.Extensions.Logging.Abstractions": "[10.0.10, )"
+ "BookGen.Vfs": "[1.0.0, )"
}
},
"bookgen.contents": {
@@ -91,7 +55,6 @@
"bookgen.shell.shared": {
"type": "Project",
"dependencies": {
- "Microsoft.Extensions.Logging.Abstractions": "[10.0.10, )",
"Spectre.Console": "[0.57.2, )",
"Webmaster442.WindowsTerminal": "[4.1.1, )"
}
diff --git a/Source/BookGen.Vfs/BookGen.Vfs.csproj b/Source/BookGen.Vfs/BookGen.Vfs.csproj
index 0eef9a3a..3264c7d2 100644
--- a/Source/BookGen.Vfs/BookGen.Vfs.csproj
+++ b/Source/BookGen.Vfs/BookGen.Vfs.csproj
@@ -1,7 +1,7 @@
- net10.0
+ net11.0
enable
enable
..\..\bin\$(Configuration)\
diff --git a/Source/BookGen.Vfs/FileSystemChange.cs b/Source/BookGen.Vfs/FileSystemChange.cs
new file mode 100644
index 00000000..aec7d9a4
--- /dev/null
+++ b/Source/BookGen.Vfs/FileSystemChange.cs
@@ -0,0 +1,21 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+namespace BookGen.Vfs;
+
+public sealed class FileSystemChangeEventArgs : EventArgs
+{
+ public required string FileName { get; init; }
+ public string? NewFileName { get; init; }
+ public required Change ChangeType { get; init; }
+
+ public enum Change
+ {
+ Created,
+ Changed,
+ Deleted,
+ Renamed
+ }
+}
diff --git a/Source/BookGen.Vfs/FileSystemObserver.cs b/Source/BookGen.Vfs/FileSystemObserver.cs
new file mode 100644
index 00000000..ac3c7f47
--- /dev/null
+++ b/Source/BookGen.Vfs/FileSystemObserver.cs
@@ -0,0 +1,85 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+using System.Diagnostics;
+
+using Microsoft.Extensions.Logging;
+
+namespace BookGen.Vfs;
+
+public sealed class FileSystemObserver : IFileSystemObserver
+{
+ private readonly FileSystemWatcher _watcher;
+ private readonly ILogger _logger;
+
+ public FileSystemObserver(ILogger logger, string folder, string filter)
+ {
+ _logger = logger;
+ _watcher = new FileSystemWatcher
+ {
+ Filter = filter,
+ Path = folder,
+ NotifyFilter = NotifyFilters.Attributes
+ | NotifyFilters.DirectoryName
+ | NotifyFilters.FileName
+ | NotifyFilters.LastWrite
+ | NotifyFilters.Size,
+ IncludeSubdirectories = true,
+ };
+ _watcher.Changed += OnChanged;
+ _watcher.Created += OnChanged;
+ _watcher.Deleted += OnChanged;
+ _watcher.Renamed += OnRenamed;
+ _watcher.Error += OnError;
+ _watcher.EnableRaisingEvents = true;
+ }
+
+ public void Dispose()
+ {
+ _watcher.Changed -= OnChanged;
+ _watcher.Created -= OnChanged;
+ _watcher.Deleted -= OnChanged;
+ _watcher.Renamed -= OnRenamed;
+ _watcher.Error -= OnError;
+ _watcher.Dispose();
+ }
+
+ public event EventHandler? FileChanged;
+
+ private void OnError(object sender, ErrorEventArgs e)
+ {
+ _logger.LogError(e.GetException(), "File system watcher error");
+ }
+
+ private void OnRenamed(object sender, RenamedEventArgs e)
+ {
+ FileChanged?.Invoke(this, new FileSystemChangeEventArgs
+ {
+ FileName = e.OldFullPath,
+ NewFileName = e.FullPath,
+ ChangeType = FileSystemChangeEventArgs.Change.Renamed,
+ });
+ }
+
+ private void OnChanged(object sender, FileSystemEventArgs e)
+ {
+ FileChanged?.Invoke(this, new FileSystemChangeEventArgs
+ {
+ FileName = e.FullPath,
+ ChangeType = Map(e.ChangeType),
+ });
+ }
+
+ private static FileSystemChangeEventArgs.Change Map(WatcherChangeTypes changeType)
+ {
+ return changeType switch
+ {
+ WatcherChangeTypes.Created => FileSystemChangeEventArgs.Change.Created,
+ WatcherChangeTypes.Changed => FileSystemChangeEventArgs.Change.Changed,
+ WatcherChangeTypes.Deleted => FileSystemChangeEventArgs.Change.Deleted,
+ _ => throw new UnreachableException()
+ };
+ }
+}
diff --git a/Source/BookGen.Vfs/IFileSystemObserver.cs b/Source/BookGen.Vfs/IFileSystemObserver.cs
new file mode 100644
index 00000000..48315a0e
--- /dev/null
+++ b/Source/BookGen.Vfs/IFileSystemObserver.cs
@@ -0,0 +1,11 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+namespace BookGen.Vfs;
+
+public interface IFileSystemObserver : IDisposable
+{
+ event EventHandler? FileChanged;
+}
diff --git a/Source/BookGen.Vfs/IReadOnlyFileSystem.cs b/Source/BookGen.Vfs/IReadOnlyFileSystem.cs
index 51cf4509..746e3e70 100644
--- a/Source/BookGen.Vfs/IReadOnlyFileSystem.cs
+++ b/Source/BookGen.Vfs/IReadOnlyFileSystem.cs
@@ -3,6 +3,8 @@
// This code is licensed under MIT license (see LICENSE for details)
//-----------------------------------------------------------------------------
+using Microsoft.Extensions.Logging;
+
namespace BookGen.Vfs;
public interface IReadOnlyFileSystem
@@ -18,4 +20,5 @@ public interface IReadOnlyFileSystem
bool DirectoryExists(string path);
IEnumerable GetFiles(string path, string filter, bool recursive);
IEnumerable GetDirectories(string path, bool recursive);
+ IFileSystemObserver CreateObserver(ILogger logger, string filter = "*.*");
}
diff --git a/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs b/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs
index 1f683814..a4cad351 100644
--- a/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs
+++ b/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs
@@ -3,6 +3,8 @@
// This code is licensed under MIT license (see LICENSE for details)
//-----------------------------------------------------------------------------
+using Microsoft.Extensions.Logging;
+
namespace BookGen.Vfs;
public sealed class MultiReadScopeFileSystem : IReadOnlyFileSystem
@@ -119,4 +121,15 @@ public async Task ReadAllTextAsync(string path)
var actualPath = Resolve(path);
return await File.ReadAllTextAsync(actualPath);
}
+
+ public IFileSystemObserver CreateObserver(ILogger logger, string filter = "*.*")
+ {
+ if (_scopes.Count < 1)
+ throw new InvalidOperationException("Scope must be defined to create a file system observer.");
+
+ if (_scopes.Count > 1)
+ throw new InvalidOperationException("MultiReadScopeFileSystem does not support file system observers with multiple scopes.");
+
+ return new FileSystemObserver(logger, Path.GetFullPath(_scopes.First()), filter);
+ }
}
diff --git a/Source/BookGen.Vfs/ReadOnlyFileSystem.cs b/Source/BookGen.Vfs/ReadOnlyFileSystem.cs
index 2a506fee..852cd9a1 100644
--- a/Source/BookGen.Vfs/ReadOnlyFileSystem.cs
+++ b/Source/BookGen.Vfs/ReadOnlyFileSystem.cs
@@ -5,6 +5,8 @@
using System.Diagnostics;
+using Microsoft.Extensions.Logging;
+
namespace BookGen.Vfs;
[DebuggerDisplay("{Scope}")]
@@ -103,4 +105,11 @@ public async Task ReadAllTextAsync(string path)
var actualPath = GetAndValidateFullNameInScope(path);
return await File.ReadAllTextAsync(actualPath);
}
+
+ public IFileSystemObserver CreateObserver(ILogger logger, string filter = "*.*")
+ {
+ return string.IsNullOrEmpty(Scope)
+ ? throw new InvalidOperationException("Scope must be defined to create a file system observer.")
+ : (IFileSystemObserver)new FileSystemObserver(logger, Path.GetFullPath(Scope), filter);
+ }
}
diff --git a/Source/BookGen/BookGen.csproj b/Source/BookGen/BookGen.csproj
index 2c58dda1..596c336e 100644
--- a/Source/BookGen/BookGen.csproj
+++ b/Source/BookGen/BookGen.csproj
@@ -2,7 +2,7 @@
Exe
- net10.0
+ net11.0
enable
enable
true
@@ -30,12 +30,9 @@
-
-
-
all
diff --git a/Source/BookGen/Commands/Convert/Diagram2SvgCommand.cs b/Source/BookGen/Commands/Convert/Diagram2SvgCommand.cs
new file mode 100644
index 00000000..76e328f5
--- /dev/null
+++ b/Source/BookGen/Commands/Convert/Diagram2SvgCommand.cs
@@ -0,0 +1,101 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+using System.ComponentModel;
+
+using BookGen.Cli;
+using BookGen.Cli.Annotations;
+using BookGen.Lib.AppSettings;
+using BookGen.Lib.Rendering.Images;
+using BookGen.Lib.Rendering.Markdown.RenderInterop;
+using BookGen.Vfs;
+
+using Microsoft.Extensions.Logging;
+
+namespace BookGen.Commands.Convert;
+
+[CommandName("convert diagram2svg")]
+[Description("Renders a diagram definition file to an SVG file.")]
+[ExitCode(ExitCodes.Success, "The command completed successfully.")]
+internal sealed class Diagram2SvgCommand : AsyncCommand
+{
+ internal sealed class Arguments : ArgumentsBase
+ {
+ internal enum DiagramType
+ {
+ Unknown,
+ Mermaid,
+ Nomnoml,
+ }
+
+ [Switch("i", "input", Required = true)]
+ [Description("Input file containing the diagram definition.")]
+ public string InputFile { get; set; } = string.Empty;
+
+ [Switch("o", "output", Required = true)]
+ [Description("Output file where the rendered SVG will be saved.")]
+ public string OutputFile { get; set; } = string.Empty;
+
+ [Switch("t", "type", Required = true)]
+ [Description("Type of the diagram to render. Can be Mermaid or Nomnoml.")]
+ public DiagramType Type { get; set; } = DiagramType.Unknown;
+
+ public override ValidationResult Validate(IValidationContext context)
+ {
+ ValidationResult result = new();
+
+ if (string.IsNullOrEmpty(InputFile))
+ result.AddIssue("Input file can't be empty");
+
+ if (!context.FileSystem.FileExists(InputFile))
+ result.AddIssue($"Input file '{InputFile}' does not exist");
+
+ if (string.IsNullOrEmpty(OutputFile))
+ result.AddIssue("Output file must be specified");
+
+ if (Type == DiagramType.Unknown)
+ result.AddIssue("Diagram type must be specified");
+
+ return result;
+ }
+
+ public override void ModifyAfterValidation()
+ {
+ OutputFile = Path.ChangeExtension(OutputFile, ".svg");
+ }
+ }
+
+ private readonly ILogger _log;
+ private readonly IWritableFileSystem _fileSystem;
+ private readonly IProgramPathResolver _programPathResolver;
+ private readonly IAssetSource _assetSource;
+
+ public Diagram2SvgCommand(ILogger log, IWritableFileSystem fileSystem, IProgramPathResolver programPathResolver, IAssetSource assetSource)
+ {
+ _log = log;
+ _fileSystem = fileSystem;
+ _programPathResolver = programPathResolver;
+ _assetSource = assetSource;
+ }
+
+ public override async Task ExecuteAsync(Arguments arguments, IReadOnlyList context, CancellationToken token)
+ {
+ using var render = IRenderInterop.CreateForSvg(_assetSource, _programPathResolver);
+
+ string inputContent = await _fileSystem.ReadAllTextAsync(arguments.InputFile);
+
+ ImageResult result = arguments.Type switch
+ {
+ Arguments.DiagramType.Mermaid => render.RenderMermaid(inputContent),
+ Arguments.DiagramType.Nomnoml => render.RenderNomnoml(inputContent),
+ _ => throw new InvalidOperationException($"Unsupported diagram type: {arguments.Type}"),
+ };
+
+ await _fileSystem.WriteAllTextAsync(arguments.OutputFile, result.Data);
+
+ return ExitCodes.Success;
+
+ }
+}
diff --git a/Source/BookGen/Commands/Folder/PreviewCommand.cs b/Source/BookGen/Commands/Folder/PreviewCommand.cs
new file mode 100644
index 00000000..861060f7
--- /dev/null
+++ b/Source/BookGen/Commands/Folder/PreviewCommand.cs
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------------
+// (c) 2019-2026 Ruzsinszki Gábor
+// This code is licensed under MIT license (see LICENSE for details)
+//-----------------------------------------------------------------------------
+
+using System.ComponentModel;
+
+using BookGen.Cli;
+using BookGen.Cli.Annotations;
+using BookGen.Lib;
+using BookGen.Lib.AppSettings;
+using BookGen.Lib.Http;
+using BookGen.Vfs;
+
+using Microsoft.Extensions.Logging;
+
+namespace BookGen.Commands.Folder;
+
+[CommandName("folder preview")]
+[Description("Starts a preview server for the specified folder.")]
+[ExitCode(ExitCodes.FolderLocked, "A serve command is running in the given folder.")]
+[ExitCode(ExitCodes.Success, "The command completed successfully.")]
+internal sealed class PreviewCommand : AsyncCommand
+{
+ private readonly IWritableFileSystem _fs;
+ private readonly ILogger _log;
+ private readonly IProgramPathResolver _programPathResolver;
+ private readonly IAssetSource _assetSource;
+
+ public PreviewCommand(IWritableFileSystem source,
+ ILogger logger,
+ IProgramPathResolver programPathResolver,
+ IAssetSource assetSource)
+ {
+ _fs = source;
+ _log = logger;
+ _programPathResolver = programPathResolver;
+ _assetSource = assetSource;
+ }
+
+ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IReadOnlyList context, CancellationToken token)
+ {
+ _fs.Scope = arguments.Directory;
+
+ using var folderLock = new FolderLock(_fs, FileNameConstants.LockFile);
+
+ if (!folderLock.Initialize())
+ {
+ _log.LogError("Failed to initialize folder lock. Another instance may be running or the directory is not writable.");
+ return ExitCodes.FolderLocked;
+ }
+
+ await using (var runner = new ConsoleHttpServerRunner(HttpServerFactory.CreateServerForPreview(_fs, _log, _programPathResolver, _assetSource)))
+ {
+ var serverurls = string.Join(' ', runner.Server.GetListenUrls());
+ _log.LogInformation("Preview server working in: {directory}", arguments.Directory);
+ _log.LogInformation("Preview server running on {urls}", serverurls);
+ await runner.RunServer();
+ }
+
+ return ExitCodes.Success;
+ }
+}
diff --git a/Source/BookGen/Commands/GuiCommand.cs b/Source/BookGen/Commands/GuiCommand.cs
index 7c043a0c..3372e908 100644
--- a/Source/BookGen/Commands/GuiCommand.cs
+++ b/Source/BookGen/Commands/GuiCommand.cs
@@ -140,7 +140,7 @@ private async Task OnTest()
if (_currentArgs == null)
return ExitCodes.GeneralError;
- int result = await Run("build web", "-o", "Output/Test", "-h", $"http://localhost:{ServerFactory.HostingPort}/");
+ int result = await Run("build web", "-o", "Output/Test", "-h", $"http://localhost:{HttpServerFactory.HostingPort}/");
if (result == ExitCodes.Success)
{
diff --git a/Source/BookGen/Commands/folder/ServeCommand.cs b/Source/BookGen/Commands/folder/ServeCommand.cs
index a06ded5a..d553b597 100644
--- a/Source/BookGen/Commands/folder/ServeCommand.cs
+++ b/Source/BookGen/Commands/folder/ServeCommand.cs
@@ -44,7 +44,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea
#pragma warning disable CA2000 // Dispose objects before losing scope
//runner is responsible for disposing the server
- await using (var runner = new ConsoleHttpServerRunner(ServerFactory.CreateServerForDirectoryHosting(arguments.Directory, _log)))
+ await using (var runner = new ConsoleHttpServerRunner(HttpServerFactory.CreateServerForDirectoryHosting(arguments.Directory, _log)))
{
var serverurls = string.Join(' ', runner.Server.GetListenUrls());
var qrcodes = string.Join(' ', runner.Server.GetListenUrls().Select(x => $"{x}/qrcodelink"));
diff --git a/Source/BookGen/Properties/launchSettings.json b/Source/BookGen/Properties/launchSettings.json
index da26a92a..237c5735 100644
--- a/Source/BookGen/Properties/launchSettings.json
+++ b/Source/BookGen/Properties/launchSettings.json
@@ -64,6 +64,10 @@
"Build sampleplugin": {
"commandName": "Project",
"commandLineArgs": "build plugin ..\\..\\Source\\BookGen.SamplePlugin\\bin\\Debug\\netstandard2.1\\BookGen.SamplePlugin.dll -v -d ..\\..\\Docs -dev -o ..\\SamplePluginOutput"
+ },
+ "folder preview": {
+ "commandName": "Project",
+ "commandLineArgs": "folder preview -d ..\\..\\Docs"
}
}
}
\ No newline at end of file
diff --git a/Source/BookGen/Tooldownloaders/ChromaDownloader.cs b/Source/BookGen/Tooldownloaders/ChromaDownloader.cs
index 1b0f7bfd..57959af8 100644
--- a/Source/BookGen/Tooldownloaders/ChromaDownloader.cs
+++ b/Source/BookGen/Tooldownloaders/ChromaDownloader.cs
@@ -19,11 +19,7 @@ public ChromaDownloader(IApiClient apiClient,
ILogger logger)
: base(apiClient, memoryStreamManager, logger)
{
- }
-
- protected override ToolInfo CreateToolInfo()
- {
- return new ToolInfo
+ ToolInfo = new ToolInfo
{
Name = "Chroma",
ApproximateSize = "8 MiB",
@@ -33,6 +29,8 @@ protected override ToolInfo CreateToolInfo()
};
}
+ public override ToolInfo ToolInfo { get; }
+
protected override Task Extract(IDownloadUi ui, Stream stream)
=> Extractor.ExtractTarGz(ui, stream, ToolInfo.FolderName);
diff --git a/Source/BookGen/Tooldownloaders/CopyPartyDownloader.cs b/Source/BookGen/Tooldownloaders/CopyPartyDownloader.cs
index a9a217fa..e216d80b 100644
--- a/Source/BookGen/Tooldownloaders/CopyPartyDownloader.cs
+++ b/Source/BookGen/Tooldownloaders/CopyPartyDownloader.cs
@@ -19,11 +19,7 @@ public CopyPartyDownloader(IApiClient apiClient,
ILogger logger)
: base(apiClient, memoryStreamManager, logger)
{
- }
-
- protected override ToolInfo CreateToolInfo()
- {
- return new ToolInfo
+ ToolInfo = new ToolInfo
{
Name = "Copyparty.exe",
ApproximateSize = "13 MiB",
@@ -33,6 +29,8 @@ protected override ToolInfo CreateToolInfo()
};
}
+ public override ToolInfo ToolInfo { get; }
+
protected override ReleaseAsset? GetReleaseAsset(IEnumerable releaseAssets)
{
return releaseAssets
diff --git a/Source/BookGen/Tooldownloaders/GithubDownloader.cs b/Source/BookGen/Tooldownloaders/GithubDownloader.cs
index b1ce9e6b..4b4f5a76 100644
--- a/Source/BookGen/Tooldownloaders/GithubDownloader.cs
+++ b/Source/BookGen/Tooldownloaders/GithubDownloader.cs
@@ -19,11 +19,7 @@ public GithubDownloader(IApiClient apiClient,
ILogger logger)
: base(apiClient, memoryStreamManager, logger)
{
- }
-
- protected override ToolInfo CreateToolInfo()
- {
- return new ToolInfo
+ ToolInfo = new ToolInfo
{
Name = "Github CLI",
ApproximateSize = "38 MiB",
@@ -33,6 +29,8 @@ protected override ToolInfo CreateToolInfo()
};
}
+ public override ToolInfo ToolInfo { get; }
+
protected override ReleaseAsset? GetReleaseAsset(IEnumerable releaseAssets)
{
return releaseAssets
diff --git a/Source/BookGen/Tooldownloaders/GlowDownloader.cs b/Source/BookGen/Tooldownloaders/GlowDownloader.cs
index 1d896cee..ec233250 100644
--- a/Source/BookGen/Tooldownloaders/GlowDownloader.cs
+++ b/Source/BookGen/Tooldownloaders/GlowDownloader.cs
@@ -19,11 +19,7 @@ public GlowDownloader(IApiClient apiClient,
ILogger log)
: base(apiClient, memoryStreamManager, log)
{
- }
-
- protected override ToolInfo CreateToolInfo()
- {
- return new ToolInfo
+ ToolInfo = new ToolInfo
{
Name = "Glow",
ApproximateSize = "18 MiB",
@@ -33,6 +29,8 @@ protected override ToolInfo CreateToolInfo()
};
}
+ public override ToolInfo ToolInfo { get; }
+
protected override ReleaseAsset? GetReleaseAsset(IEnumerable releaseAssets)
{
return releaseAssets
diff --git a/Source/BookGen/Tooldownloaders/MicrosoftEditToolDownloader.cs b/Source/BookGen/Tooldownloaders/MicrosoftEditToolDownloader.cs
index 3b6fa854..b871d28c 100644
--- a/Source/BookGen/Tooldownloaders/MicrosoftEditToolDownloader.cs
+++ b/Source/BookGen/Tooldownloaders/MicrosoftEditToolDownloader.cs
@@ -19,11 +19,7 @@ public MicrosoftEditToolDownloader(IApiClient apiClient,
ILogger logger)
: base(apiClient, memoryStreamManager, logger)
{
- }
-
- protected override ToolInfo CreateToolInfo()
- {
- return new ToolInfo
+ ToolInfo = new ToolInfo
{
Name = "Microsoft Edit",
ApproximateSize = "3.6 MiB",
@@ -33,6 +29,8 @@ protected override ToolInfo CreateToolInfo()
};
}
+ public override ToolInfo ToolInfo { get; }
+
protected override ReleaseAsset? GetReleaseAsset(IEnumerable releaseAssets)
{
return releaseAssets
diff --git a/Source/BookGen/Tooldownloaders/PandocTooldownloader.cs b/Source/BookGen/Tooldownloaders/PandocTooldownloader.cs
index acf2fd87..c657daeb 100644
--- a/Source/BookGen/Tooldownloaders/PandocTooldownloader.cs
+++ b/Source/BookGen/Tooldownloaders/PandocTooldownloader.cs
@@ -19,11 +19,7 @@ public PandocTooldownloader(IApiClient apiClient,
ILogger logger)
: base(apiClient, memoryStreamManager, logger)
{
- }
-
- protected override ToolInfo CreateToolInfo()
- {
- return new ToolInfo
+ ToolInfo = new ToolInfo
{
Name = "Pandoc",
ApproximateSize = "217 MiB",
@@ -33,6 +29,8 @@ protected override ToolInfo CreateToolInfo()
};
}
+ public override ToolInfo ToolInfo { get; }
+
protected override ReleaseAsset? GetReleaseAsset(IEnumerable releaseAssets)
{
return releaseAssets
diff --git a/Source/BookGen/Tooldownloaders/TooldownloaderBase.cs b/Source/BookGen/Tooldownloaders/TooldownloaderBase.cs
index 26a18b59..906ec176 100644
--- a/Source/BookGen/Tooldownloaders/TooldownloaderBase.cs
+++ b/Source/BookGen/Tooldownloaders/TooldownloaderBase.cs
@@ -25,12 +25,9 @@ public TooldownloaderBase(IApiClient apiClient,
_apiClient = apiClient;
_memoryStreamManager = memoryStreamManager;
_log = log;
- ToolInfo = CreateToolInfo();
}
- protected abstract ToolInfo CreateToolInfo();
-
- public ToolInfo ToolInfo { get; }
+ public abstract ToolInfo ToolInfo { get; }
protected abstract ReleaseAsset? GetReleaseAsset(IEnumerable releaseAssets);
diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json
index b7e2c379..f454e4eb 100644
--- a/Source/BookGen/packages.lock.json
+++ b/Source/BookGen/packages.lock.json
@@ -1,7 +1,7 @@
{
"version": 2,
"dependencies": {
- "net10.0": {
+ "net11.0": {
"HtmlToOpenXml.dll": {
"type": "Direct",
"requested": "[3.5.0, )",
@@ -9,8 +9,7 @@
"contentHash": "ULQqQidtvWfGhds11YWG0uAkRYGXFzn3mNA6ZVnByJUV/1pqkR4fVAF3f7V1cSbpdSWCwoPtFbAef//rUNCmKA==",
"dependencies": {
"AngleSharp": "1.5.0",
- "DocumentFormat.OpenXml": "3.5.1",
- "Microsoft.Extensions.Logging.Abstractions": "6.0.0"
+ "DocumentFormat.OpenXml": "3.5.1"
}
},
"Markdig": {
@@ -19,68 +18,32 @@
"resolved": "1.3.2",
"contentHash": "fZgOC/3CswUrndjDTac70aQpYdtxbW5+5bRumR7vzvI2HJbkmgKisB1c9oT+GA6v0jB/JDR9BLa9FiPzQmaK6A=="
},
- "Microsoft.Extensions.Caching.Abstractions": {
- "type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "4ZFBNE+jzR+CrWWlhOesnmywCW7pYKT0dxyAQRdL11yJwxe4jvcAu31eorFtEkoFeCDcUTeNssgPv2yaRRptaQ==",
- "dependencies": {
- "Microsoft.Extensions.Primitives": "10.0.10"
- }
- },
"Microsoft.Extensions.Caching.Memory": {
"type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "N1w5H7uK6gCTnCBZAWzE0/EQYSPysij/uYwDqntqBVvBa6bjMmBKitsnEFd6yh/SX3wLm67nO6+OnZ84K+gZWg==",
- "dependencies": {
- "Microsoft.Extensions.Caching.Abstractions": "10.0.10",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
- "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
- "Microsoft.Extensions.Options": "10.0.10",
- "Microsoft.Extensions.Primitives": "10.0.10"
- }
+ "requested": "[11.0.0-preview.6.26359.118, )",
+ "resolved": "11.0.0-preview.6.26359.118",
+ "contentHash": "/0XbQexXoSNQ7YfwhiI8XJvK05+rHTVxJZzuMCFc/l8owJ7aPwwE6cYwCDs1LvsklOWyfGm1LaAUHQbhYXNNcw=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "ANyvsgkNBRvcJh2XLgn8veGmajf+8m0AbKK+HPWdRL1yraSNVVSmQhFntLtdz/C795jxqqup+k05cs/3jZQPOA==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10"
- }
- },
- "Microsoft.Extensions.DependencyInjection.Abstractions": {
- "type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "z/2xXlFw2aLGjHyEm6E0tQ+In6VfzQzTrtArbQ2c0TQE16ZbyDCMGPvaUT9I0s8rgy9sRWlU2P9waW37qV04qA=="
+ "requested": "[11.0.0-preview.6.26359.118, )",
+ "resolved": "11.0.0-preview.6.26359.118",
+ "contentHash": "MwlfMwxXewuq+X85a31yYx6emRYIyJOboVXZtRH3Wvp/hZZWmlg1pu/tI+XJUm4ltoCNMzpcFF6zJcmbnbnuGA=="
},
"Microsoft.Extensions.Logging": {
"type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "Tf6z5HsL0VDYRTfvsoNrTGHGheCwkTsZBA2FFh5ATJUbkAwug+FFNISJK2gjpUNemlAOoWllAK52HOWCjto3EQ==",
+ "requested": "[11.0.0-preview.6.26359.118, )",
+ "resolved": "11.0.0-preview.6.26359.118",
+ "contentHash": "lUAZ402asMsLYe483nXXQIphklpTNDP6LmhmrwB8V3gBTjXeUp40N6h/Ap5QOiGHqsAiooznL4lBETGSncYsxQ==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection": "10.0.10",
- "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
- "Microsoft.Extensions.Options": "10.0.10"
- }
- },
- "Microsoft.Extensions.Logging.Abstractions": {
- "type": "Direct",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "zkFxGYUvdxAvIKTyXHrmW+Sux53D4SezD9dMyZ6hrwwzPQJNuwCRy1f5W7AvYTqacEGhWF2XderRQG1OvbV8og==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10"
+ "Microsoft.Extensions.DependencyInjection": "11.0.0-preview.6.26359.118"
}
},
"Microsoft.IO.RecyclableMemoryStream": {
"type": "Direct",
- "requested": "[3.0.1, )",
- "resolved": "3.0.1",
- "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g=="
+ "requested": "[4.0.0-preview, )",
+ "resolved": "4.0.0-preview",
+ "contentHash": "ueAhj4Qj4HgGyS0+efG2IjvqYHPWkayE+GWUnseWe6aJQagoxHWr2ljabmXd9k4QpSH2jpoTd7TTzgUsc2selA=="
},
"Roslynator.Analyzers": {
"type": "Direct",
@@ -195,20 +158,6 @@
"Microsoft.ClearScript.Core": "7.5.1"
}
},
- "Microsoft.Extensions.Options": {
- "type": "Transitive",
- "resolved": "10.0.10",
- "contentHash": "srnhnk7nE8krBiIXp71LvBmKBtraBONWSRzdjJgRv1Ko9Mp8IVNqv4vIS9hGeVteBig8aQkva9ZG+sC+o5sVcA==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
- "Microsoft.Extensions.Primitives": "10.0.10"
- }
- },
- "Microsoft.Extensions.Primitives": {
- "type": "Transitive",
- "resolved": "10.0.10",
- "contentHash": "5wu/GrYVd8mG2DVUw3vFJzF+O336TyTGg/Kmcgw9bfwYhCoFiV5lR5QeEmKecJyrW4W54nMfD3p3589E8a7czQ=="
- },
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
@@ -278,9 +227,7 @@
"bookgen.cli": {
"type": "Project",
"dependencies": {
- "BookGen.Vfs": "[1.0.0, )",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.10, )",
- "Microsoft.Extensions.Logging.Abstractions": "[10.0.10, )"
+ "BookGen.Vfs": "[1.0.0, )"
}
},
"bookgen.contents": {
@@ -301,14 +248,13 @@
"SkiaSharp.NativeAssets.Linux": "[4.150.1, )",
"SkiaSharp.NativeAssets.Win32": "[4.150.1, )",
"Svg.Skia": "[5.1.1, )",
- "System.ServiceModel.Syndication": "[10.0.10, )",
+ "System.ServiceModel.Syndication": "[11.0.0-preview.6.26359.118, )",
"YamlDotNet": "[18.1.0, )"
}
},
"bookgen.shell.shared": {
"type": "Project",
"dependencies": {
- "Microsoft.Extensions.Logging.Abstractions": "[10.0.10, )",
"Spectre.Console": "[0.57.2, )",
"Webmaster442.WindowsTerminal": "[4.1.1, )"
}
@@ -397,9 +343,9 @@
},
"System.ServiceModel.Syndication": {
"type": "CentralTransitive",
- "requested": "[10.0.10, )",
- "resolved": "10.0.10",
- "contentHash": "jU3WQG477Tk7RYlM2Ky8oySb6WHd744LgK7MYRMe4L8FcoJI3Gbpcf29sB2lIlJVS82ah9i4FUw3RfbXeJs8jw=="
+ "requested": "[11.0.0-preview.6.26359.118, )",
+ "resolved": "11.0.0-preview.6.26359.118",
+ "contentHash": "StArv4DSO8I8QjDo1n+FzKeaq6UTNKvgkHHFaAt8ueexUWJZSEZ5/EasqwKt7413n3carQjaFzSULDI7sxwg0A=="
},
"Webmaster442.WindowsTerminal": {
"type": "CentralTransitive",
diff --git a/Test/Bookgen.Tests/Bookgen.Tests.csproj b/Test/Bookgen.Tests/Bookgen.Tests.csproj
index 1265ed44..ec0b1525 100644
--- a/Test/Bookgen.Tests/Bookgen.Tests.csproj
+++ b/Test/Bookgen.Tests/Bookgen.Tests.csproj
@@ -1,7 +1,7 @@
- net10.0
+ net11.0
latest
enable
enable
diff --git a/Test/Bookgen.Tests/EmbeddedTestFolder.cs b/Test/Bookgen.Tests/EmbeddedTestFolder.cs
index 453b7d5f..2cfa49d7 100644
--- a/Test/Bookgen.Tests/EmbeddedTestFolder.cs
+++ b/Test/Bookgen.Tests/EmbeddedTestFolder.cs
@@ -5,6 +5,8 @@
using BookGen.Vfs;
+using Microsoft.Extensions.Logging;
+
namespace Bookgen.Tests;
internal class EmbeddedTestFileSystem : IReadOnlyFileSystem
@@ -21,6 +23,11 @@ public EmbeddedTestFileSystem()
public string Scope { get; set; }
+ public IFileSystemObserver CreateObserver(ILogger logger, string filter = "*.*")
+ {
+ throw new NotImplementedException();
+ }
+
public bool DirectoryExists(string path)
{
throw new NotImplementedException();
diff --git a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter_Specials.cs b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter_Specials.cs
index 61499a5f..7be09646 100644
--- a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter_Specials.cs
+++ b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter_Specials.cs
@@ -232,4 +232,83 @@ public void EnsureThat_Math_Block_Works()
Assert.That(result, Is.EqualTo(expected).Using(comparer));
}
+
+ private const string Tsv = """
+ ```table-tsv
+ foo bar baz
+ "col11 >ong" col12 col13
+ col21 col22 col23
+ col31 col32 col33
+ ```
+ """;
+
+ private const string Csv = """
+ ```table-csv
+ foo,bar,baz
+ "col11 >ong",col12,col13
+ col21,col22,col23
+ col31,col32,col33
+ ```
+ """;
+
+ private const string Ssv = """
+ ```table-ssv
+ foo;bar;baz
+ "col11 >ong";col12;col13
+ col21;col22;col23
+ col31;col32;col33
+ ```
+ """;
+
+
+ [TestCase(Tsv)]
+ [TestCase(Csv)]
+ [TestCase(Ssv)]
+ public void EnsureThat_Language_Csv_Works(string input)
+ {
+ using var settings = new MarkdownRenderSettings(_imgServiceMock.Object)
+ {
+ CssClasses = new CssClasses(),
+ DeleteFirstH1 = false,
+ HostUrl = null,
+ AutoEmbedSupportedLinks = true,
+ RenderInterop = new RenderInterop(_testEnvironment, _testEnvironment.ProgramPathResolver, new ImageConfig
+ {
+ SvgRecode = SvgRecodeOption.Passtrough
+ })
+ };
+
+ using var sut = new MarkdownConverter(settings);
+
+ string result = sut.RenderMarkdownToHtml(input);
+
+ string expected = """
+
+
+ | foo |
+ bar |
+ baz |
+
+
+ | col11 >ong |
+ col12 |
+ col13 |
+
+
+ | col21 |
+ col22 |
+ col23 |
+
+
+ | col31 |
+ col32 |
+ col33 |
+
+
+
+ """;
+
+ Assert.That(result, Is.EqualTo(expected).Using(comparer));
+
+ }
}
diff --git a/download-assets-pipeline.sh b/download-assets-pipeline.sh
index 93cbcd40..321275e2 100644
--- a/download-assets-pipeline.sh
+++ b/download-assets-pipeline.sh
@@ -4,9 +4,9 @@ mkdir -p "./Assets/dictionaries"
touch "./Assets/dictionaries/keep.file"
cd "./Assets/tools"
-curl -L -O "https://github.com/erweixin/RaTeX/releases/download/v0.1.13/ratex-cli-v0.1.13-x86_64-unknown-linux-musl.tar.gz"
-tar -xzf ./ratex-cli-v0.1.13-x86_64-unknown-linux-musl.tar.gz -C .
-mv ./ratex-cli-v0.1.13-x86_64-unknown-linux-musl/render-svg ./ratex-svg
+curl -L -O "https://github.com/erweixin/RaTeX/releases/download/v0.1.14/ratex-cli-v0.1.14-x86_64-unknown-linux-musl.tar.gz"
+tar -xzf ./ratex-cli-v0.1.14-x86_64-unknown-linux-musl.tar.gz -C .
+mv ./ratex-cli-v0.1.14-x86_64-unknown-linux-musl/render-svg ./ratex-svg
curl -L -O "https://github.com/1jehuang/mermaid-rs-renderer/releases/download/v0.3.1/mmdr-x86_64-unknown-linux-gnu.tar.gz"
tar -xzf ./mmdr-x86_64-unknown-linux-gnu.tar.gz -C .
diff --git a/download-assets.ps1 b/download-assets.ps1
index baf2fd65..9939b156 100644
--- a/download-assets.ps1
+++ b/download-assets.ps1
@@ -27,8 +27,8 @@ function download-dictionaries {
function download-tools {
Set-Location $toolsPath
- curl.exe -L "https://github.com/erweixin/RaTeX/releases/download/v0.1.13/ratex-cli-v0.1.13-x86_64-pc-windows-msvc.zip" -o ratex-windows.zip
- curl.exe -L "https://github.com/erweixin/RaTeX/releases/download/v0.1.13/ratex-cli-v0.1.13-x86_64-unknown-linux-musl.tar.gz" -o ratex-linux.tar.gz
+ curl.exe -L "https://github.com/erweixin/RaTeX/releases/download/v0.1.14/ratex-cli-v0.1.14-x86_64-pc-windows-msvc.zip" -o ratex-windows.zip
+ curl.exe -L "https://github.com/erweixin/RaTeX/releases/download/v0.1.14/ratex-cli-v0.1.14-x86_64-unknown-linux-musl.tar.gz" -o ratex-linux.tar.gz
curl.exe -L "https://github.com/1jehuang/mermaid-rs-renderer/releases/download/v0.3.1/mmdr-x86_64-pc-windows-msvc.zip" -o mmdr-windows.zip
curl.exe -L "https://github.com/1jehuang/mermaid-rs-renderer/releases/download/v0.3.1/mmdr-x86_64-unknown-linux-gnu.tar.gz" -o mmdr-linux.tar.gz
@@ -40,14 +40,14 @@ function download-tools {
# RaTeX
Expand-Archive .\ratex-windows.zip -Force -DestinationPath .
- Move-Item -Force .\ratex-cli-v0.1.13-x86_64-pc-windows-msvc\render-svg.exe .\ratex-svg.exe
+ Move-Item -Force .\ratex-cli-v0.1.14-x86_64-pc-windows-msvc\render-svg.exe .\ratex-svg.exe
Remove-Item .\ratex-windows.zip
- Remove-Item .\ratex-cli-v0.1.13-x86_64-pc-windows-msvc -Recurse -Force
+ Remove-Item .\ratex-cli-v0.1.14-x86_64-pc-windows-msvc -Recurse -Force
tar -xzf .\ratex-linux.tar.gz -C .
- Move-Item -Force .\ratex-cli-v0.1.13-x86_64-unknown-linux-musl\render-svg .\ratex-svg
+ Move-Item -Force .\ratex-cli-v0.1.14-x86_64-unknown-linux-musl\render-svg .\ratex-svg
Remove-Item .\ratex-linux.tar.gz
- Remove-Item .\ratex-cli-v0.1.13-x86_64-unknown-linux-musl -Recurse -Force
+ Remove-Item .\ratex-cli-v0.1.14-x86_64-unknown-linux-musl -Recurse -Force
Set-Location $startdir
}