diff --git a/src/Whipstaff.Statiq/Mermaid/MermaidDiagramModule.cs b/src/Whipstaff.Statiq/Mermaid/MermaidDiagramModule.cs new file mode 100644 index 000000000..d138de1a1 --- /dev/null +++ b/src/Whipstaff.Statiq/Mermaid/MermaidDiagramModule.cs @@ -0,0 +1,111 @@ +// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Statiq.Common; +using Whipstaff.Mermaid.HttpServer; +using Whipstaff.Mermaid.Playwright; +using Whipstaff.Playwright; + +namespace Whipstaff.Statiq.Mermaid +{ + /// + /// Statiq module for producing mermaid diagrams. + /// + public sealed class MermaidDiagramModule : Module + { + private readonly System.IO.Abstractions.IFileSystem _fileSystem; + + /// + /// Initializes a new instance of the class. + /// + /// File system abstraction. + public MermaidDiagramModule(System.IO.Abstractions.IFileSystem fileSystem) + { + ArgumentNullException.ThrowIfNull(fileSystem); + _fileSystem = fileSystem; + } + + /// + protected override async Task> ExecuteInputAsync(IDocument input, IExecutionContext context) + { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(context); + + context.LogInformation(input, "Starting Mermaid Diagram CLI Module"); + + var path = _fileSystem.Path; + var inputFilename = path.GetFullPath(input.Source.FullPath); + + var rootPath = path.GetFullPath(context.FileSystem.RootPath.FullPath); + + var destination = input.Destination.FullPath; + + // this is taking the input folder, the output folder + var outputFilename = path.Combine( + rootPath, + "output", + destination); + + // and reversing the path separator on the output + outputFilename = path.GetFullPath(outputFilename); + + // finally replace the file extension + // could be a setting for now assume svg + // might be a better way of doing all the output steps + // within the Statiq Context would need to dig. + outputFilename = path.ChangeExtension( + outputFilename, + ".svg"); + + var targetDir = path.GetDirectoryName(outputFilename); + + if (targetDir == null) + { + throw new InvalidOperationException("failed to work out target directory"); + } + + var directory = _fileSystem.Directory; + if (!directory.Exists(targetDir)) + { + _ = directory.CreateDirectory(targetDir); + } + + var logMessageActions = new PlaywrightRendererLogMessageActions(); + var loggerFactory = context.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + var logMessageActionsWrapper = new PlaywrightRendererLogMessageActionsWrapper(logMessageActions, logger); + var mermaidHttpServer = MermaidHttpServerFactory.GetTestServer(loggerFactory); + + var playwrightRenderer = new Whipstaff.Mermaid.Playwright.PlaywrightRenderer( + mermaidHttpServer, + logMessageActionsWrapper); + + var markdown = await _fileSystem.File.ReadAllTextAsync(inputFilename); + + var diagramResponse = await playwrightRenderer.GetDiagram( + markdown, + PlaywrightBrowserTypeAndChannel.ChromiumDefault()) + .ConfigureAwait(false); + + if (diagramResponse == null) + { + throw new InvalidOperationException("Failed to generate diagram"); + } + + await _fileSystem.File.WriteAllTextAsync( + outputFilename, + diagramResponse.Svg) + .ConfigureAwait(false); + + // keeping the working the same as the previous cli version + // could return the response object in future. + return Array.Empty(); + } + } +} diff --git a/src/Whipstaff.Statiq/Mermaid/MermaidDiagramPipeline.cs b/src/Whipstaff.Statiq/Mermaid/MermaidDiagramPipeline.cs new file mode 100644 index 000000000..5eb23eae1 --- /dev/null +++ b/src/Whipstaff.Statiq/Mermaid/MermaidDiagramPipeline.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. +// This file is licensed to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Statiq.Common; +using Statiq.Core; + +namespace Whipstaff.Statiq.Mermaid +{ + /// + /// Statiq pipeline for processing mermaid diagram files. + /// + public sealed class MermaidDiagramPipeline : Pipeline + { + /// + /// Initializes a new instance of the class. + /// + public MermaidDiagramPipeline() + { + // we need to check for mermaidjs-cli + // we need to look for *.mmd files + var patterns = new[] { "./**/*.mmd" }; + var readFiles = new ReadFiles(patterns); + InputModules = new ModuleList(readFiles); + + ProcessModules = new ModuleList(new MermaidDiagramModule(new System.IO.Abstractions.FileSystem())); + } + } +} diff --git a/src/Whipstaff.Statiq/Whipstaff.Statiq.csproj b/src/Whipstaff.Statiq/Whipstaff.Statiq.csproj new file mode 100644 index 000000000..0c30c8572 --- /dev/null +++ b/src/Whipstaff.Statiq/Whipstaff.Statiq.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + Re-usable logic for working with WPF. + + + + + $(PackageVersion)-beta + + + + + + + + + + + + + diff --git a/src/Whipstaff.sln b/src/Whipstaff.sln index f4569787b..0d6d7fed4 100644 --- a/src/Whipstaff.sln +++ b/src/Whipstaff.sln @@ -85,7 +85,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Whipstaff.Mermaid", "Whipst EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Whipstaff.Wpf.MaterialDesign", "Whipstaff.Wpf.MaterialDesign\Whipstaff.Wpf.MaterialDesign.csproj", "{F23F9BFF-DF01-43A3-85E8-DF87CDA02A58}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Whipstaff.Wpf.AvalonEdit", "Whipstaff.Wpf.AvalonEdit\Whipstaff.Wpf.AvalonEdit.csproj", "{4E69353A-56B5-4EAE-8BE4-8975EFDB7C4D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Whipstaff.Wpf.AvalonEdit", "Whipstaff.Wpf.AvalonEdit\Whipstaff.Wpf.AvalonEdit.csproj", "{4E69353A-56B5-4EAE-8BE4-8975EFDB7C4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Whipstaff.Statiq", "Whipstaff.Statiq\Whipstaff.Statiq.csproj", "{AE602A3A-6E12-4881-8EF6-A078C11BDF0E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -257,6 +259,10 @@ Global {4E69353A-56B5-4EAE-8BE4-8975EFDB7C4D}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E69353A-56B5-4EAE-8BE4-8975EFDB7C4D}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E69353A-56B5-4EAE-8BE4-8975EFDB7C4D}.Release|Any CPU.Build.0 = Release|Any CPU + {AE602A3A-6E12-4881-8EF6-A078C11BDF0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE602A3A-6E12-4881-8EF6-A078C11BDF0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE602A3A-6E12-4881-8EF6-A078C11BDF0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE602A3A-6E12-4881-8EF6-A078C11BDF0E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE