From 9862c653d9a27a7974ac64d19f0e7f242a49d110 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sun, 16 Nov 2025 21:22:44 +0100 Subject: [PATCH 01/41] Packages update & shell fix --- BookGen.slnx | 1 + Directory.Packages.props | 2 +- Installers/prepare-debian-base.sh | 7 ------- Source/BookGen.Contents/BookGenShell.ps1 | 9 +++++---- Source/BookGen.Shell.Shared/packages.lock.json | 6 +++--- Source/BookGen.Shellprog/packages.lock.json | 8 ++++---- Source/BookGen/packages.lock.json | 8 ++++---- Source/Bookgen.Lib/packages.lock.json | 8 ++++---- 8 files changed, 22 insertions(+), 27 deletions(-) diff --git a/BookGen.slnx b/BookGen.slnx index f5b834a6..ec74988d 100644 --- a/BookGen.slnx +++ b/BookGen.slnx @@ -14,6 +14,7 @@ + diff --git a/Directory.Packages.props b/Directory.Packages.props index c8299178..47e924a6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -26,7 +26,7 @@ - + diff --git a/Installers/prepare-debian-base.sh b/Installers/prepare-debian-base.sh index 8506a236..a59c19e3 100644 --- a/Installers/prepare-debian-base.sh +++ b/Installers/prepare-debian-base.sh @@ -9,12 +9,5 @@ fi apt update apt dist-upgrade -y apt install -y curl wget mc - -wget https://packages.microsoft.com/config/debian/13/packages-microsoft-prod.deb -O packages-microsoft-prod.deb -sudo dpkg -i packages-microsoft-prod.deb -rm packages-microsoft-prod.deb - -apt update -apt install -y dotnet-sdk-10.0 apt autoremove --purge apt clean \ No newline at end of file diff --git a/Source/BookGen.Contents/BookGenShell.ps1 b/Source/BookGen.Contents/BookGenShell.ps1 index 66978089..25a917c8 100644 --- a/Source/BookGen.Contents/BookGenShell.ps1 +++ b/Source/BookGen.Contents/BookGenShell.ps1 @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BookGen PowerShell Registration script -# Version 3.7.2 -# Last modified: 2025-10-05 +# Version 3.8.0 +# Last modified: 2025-11-16 # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- @@ -252,18 +252,19 @@ function weather { [string]$Location ) - $url = "https://wttr.in/" + $url = "https://wttr.in/?n" if ($PSBoundParameters.ContainsKey('Location')) { $url += $Location } Clear-Host - curl $url + Invoke-WebRequest $url } # intro message function intro() { + BookGen.Shellprog.exe "terminalsize" Clear-Host bookgen version -nr Write-Host "┌──────────────────────────────────────────────────────────┐" diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json index 64963dcd..5797d95f 100644 --- a/Source/BookGen.Shell.Shared/packages.lock.json +++ b/Source/BookGen.Shell.Shared/packages.lock.json @@ -13,9 +13,9 @@ }, "Webmaster442.WindowsTerminal": { "type": "Direct", - "requested": "[4.0.0, )", - "resolved": "4.0.0", - "contentHash": "NG6kGSt83OWcRRVdzD5D8O4xWupE0FiFxTIz9dnm+jt1eK3DYyEhXX939vOyXSVVz3xFUhldKchOrtJSJx35kA==" + "requested": "[4.1.1, )", + "resolved": "4.1.1", + "contentHash": "0z1y6nFmpAhHP0x/aus62qIS+MDwntC8JYvkqEm5v9QyszzmyQG5Jp01a6FYacaXZ7CsvQgV7TzhA6G6p6MTSA==" }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json index 99a423f3..e8d77060 100644 --- a/Source/BookGen.Shellprog/packages.lock.json +++ b/Source/BookGen.Shellprog/packages.lock.json @@ -128,7 +128,7 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "[10.0.0, )", - "Webmaster442.WindowsTerminal": "[4.0.0, )" + "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, "bookgen.vfs": { @@ -151,9 +151,9 @@ }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", - "requested": "[4.0.0, )", - "resolved": "4.0.0", - "contentHash": "NG6kGSt83OWcRRVdzD5D8O4xWupE0FiFxTIz9dnm+jt1eK3DYyEhXX939vOyXSVVz3xFUhldKchOrtJSJx35kA==" + "requested": "[4.1.1, )", + "resolved": "4.1.1", + "contentHash": "0z1y6nFmpAhHP0x/aus62qIS+MDwntC8JYvkqEm5v9QyszzmyQG5Jp01a6FYacaXZ7CsvQgV7TzhA6G6p6MTSA==" } } } diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index 16218a35..c61bcf56 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -250,7 +250,7 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "[10.0.0, )", - "Webmaster442.WindowsTerminal": "[4.0.0, )" + "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, "bookgen.vfs": { @@ -328,9 +328,9 @@ }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", - "requested": "[4.0.0, )", - "resolved": "4.0.0", - "contentHash": "NG6kGSt83OWcRRVdzD5D8O4xWupE0FiFxTIz9dnm+jt1eK3DYyEhXX939vOyXSVVz3xFUhldKchOrtJSJx35kA==" + "requested": "[4.1.1, )", + "resolved": "4.1.1", + "contentHash": "0z1y6nFmpAhHP0x/aus62qIS+MDwntC8JYvkqEm5v9QyszzmyQG5Jp01a6FYacaXZ7CsvQgV7TzhA6G6p6MTSA==" }, "YamlDotNet": { "type": "CentralTransitive", diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index 7ab38e81..7391fac1 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -159,7 +159,7 @@ "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Webmaster442.WindowsTerminal": "[4.0.0, )" + "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, "bookgen.vfs": { @@ -167,9 +167,9 @@ }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", - "requested": "[4.0.0, )", - "resolved": "4.0.0", - "contentHash": "NG6kGSt83OWcRRVdzD5D8O4xWupE0FiFxTIz9dnm+jt1eK3DYyEhXX939vOyXSVVz3xFUhldKchOrtJSJx35kA==" + "requested": "[4.1.1, )", + "resolved": "4.1.1", + "contentHash": "0z1y6nFmpAhHP0x/aus62qIS+MDwntC8JYvkqEm5v9QyszzmyQG5Jp01a6FYacaXZ7CsvQgV7TzhA6G6p6MTSA==" } } } From 50c169b27d7c63ab1d2233e3a242003505ad252a Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sun, 16 Nov 2025 21:23:06 +0100 Subject: [PATCH 02/41] Packages update & shell fix --- Source/BookGen.Contents/BookGenShell.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/BookGen.Contents/BookGenShell.ps1 b/Source/BookGen.Contents/BookGenShell.ps1 index 25a917c8..ae5a17d4 100644 --- a/Source/BookGen.Contents/BookGenShell.ps1 +++ b/Source/BookGen.Contents/BookGenShell.ps1 @@ -259,12 +259,11 @@ function weather { } Clear-Host - Invoke-WebRequest $url + (Invoke-WebRequest $url).Content } # intro message function intro() { - BookGen.Shellprog.exe "terminalsize" Clear-Host bookgen version -nr Write-Host "┌──────────────────────────────────────────────────────────┐" From 3fb0f03326d7c468ae4d8ebd939132acd9fad228 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Thu, 18 Dec 2025 18:19:26 +0100 Subject: [PATCH 03/41] Glow added to tools --- Source/BookGen.Contents/BookGenShell.ps1 | 9 ++++ Source/BookGen/Commands/ToolsCommand.cs | 1 + Source/BookGen/Program.cs | 9 ++-- .../BookGen/Tooldownloaders/GlowDownloader.cs | 44 +++++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 Source/BookGen/Tooldownloaders/GlowDownloader.cs diff --git a/Source/BookGen.Contents/BookGenShell.ps1 b/Source/BookGen.Contents/BookGenShell.ps1 index ae5a17d4..a845e519 100644 --- a/Source/BookGen.Contents/BookGenShell.ps1 +++ b/Source/BookGen.Contents/BookGenShell.ps1 @@ -173,6 +173,15 @@ function gh { GetTool "gh" "github-cli" @Args } +function glow { + param ( + [string[]] + [Parameter(ValueFromRemainingArguments = $true)] + $Args + ) + GetTool "glow" "glow" @Args +} + function copyparty { param ( [string[]] diff --git a/Source/BookGen/Commands/ToolsCommand.cs b/Source/BookGen/Commands/ToolsCommand.cs index c4760aa5..5c64cdf7 100644 --- a/Source/BookGen/Commands/ToolsCommand.cs +++ b/Source/BookGen/Commands/ToolsCommand.cs @@ -36,6 +36,7 @@ public ToolsCommand(IApiClient apiClient, ILogger logger) new ChromaDownloader(apiClient, _memoryStreamManager, _logger), new CopyPartyDownloader(apiClient, _memoryStreamManager, _logger), new GithubDownloader(apiClient, _memoryStreamManager, _logger), + new GlowDownloader(apiClient, _memoryStreamManager, _logger), new MicrosoftEditToolDownloader(apiClient, _memoryStreamManager, _logger), new PandocTooldownloader(apiClient, _memoryStreamManager, _logger), ]; diff --git a/Source/BookGen/Program.cs b/Source/BookGen/Program.cs index 28fcbb5e..f274c009 100644 --- a/Source/BookGen/Program.cs +++ b/Source/BookGen/Program.cs @@ -62,10 +62,11 @@ ExcptionExitCode = -1, PlatformNotSupportedExitCode = 4, EnableUtf8Output = true, -}); - -runner.ExceptionHandlerDelegate = OnException; -runner.BeforeRunHook = OnBeforeRun; +}) +{ + ExceptionHandlerDelegate = OnException, + BeforeRunHook = OnBeforeRun +}; runner .AddDefaultCommand() diff --git a/Source/BookGen/Tooldownloaders/GlowDownloader.cs b/Source/BookGen/Tooldownloaders/GlowDownloader.cs new file mode 100644 index 00000000..8bcecb3f --- /dev/null +++ b/Source/BookGen/Tooldownloaders/GlowDownloader.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2025 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Bookgen.Lib.Domain.Github; + +using BookGen.Infrastructure.Tools; +using BookGen.Vfs; + +using Microsoft.Extensions.Logging; +using Microsoft.IO; + +namespace BookGen.Tooldownloaders; + +internal sealed class GlowDownloader : TooldownloaderBase +{ + public GlowDownloader(IApiClient apiClient, + RecyclableMemoryStreamManager memoryStreamManager, + ILogger log) + : base(apiClient, memoryStreamManager, log) + { + } + + protected override ToolInfo CreateToolInfo() + { + return new ToolInfo + { + Name = "Glow", + ApproximateSize = "18 MiB", + RepoOwner = "charmbracelet", + RepoName = "glow", + FolderName = "glow", + }; + } + + protected override ReleaseAsset? GetReleaseAsset(IEnumerable releaseAssets) + { + return releaseAssets + .Where(r => r.Name.EndsWith("Windows_x86_64.zip")) + .OrderByDescending(r => r.CreatedAt) + .FirstOrDefault(); + } +} From 97d424bce2a7d105a14c966809c3ccb521fa1b00 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sat, 27 Dec 2025 18:46:10 +0100 Subject: [PATCH 04/41] Package update --- Directory.Packages.props | 14 +- Source/BookGen.Cli/packages.lock.json | 14 +- .../BookGen.Shell.Shared/packages.lock.json | 14 +- Source/BookGen.Shellprog/packages.lock.json | 122 ++++++++-------- Source/BookGen/packages.lock.json | 138 +++++++++--------- Source/Bookgen.Lib/packages.lock.json | 12 +- 6 files changed, 157 insertions(+), 157 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 47e924a6..a15ea897 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,27 +5,27 @@ - + - - - - + + + + - + - + diff --git a/Source/BookGen.Cli/packages.lock.json b/Source/BookGen.Cli/packages.lock.json index 9584c2fd..e1bba11c 100644 --- a/Source/BookGen.Cli/packages.lock.json +++ b/Source/BookGen.Cli/packages.lock.json @@ -4,17 +4,17 @@ "net10.0": { "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "L3AdmZ1WOK4XXT5YFPEwyt0ep6l8lGIPs7F5OOBZc77Zqeo01Of7XXICy47628sdVl0v/owxYJTe86DTgFwKCA==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "FU/IfjDfwaMuKr414SSQNTIti/69bHEMb+QKrskRb26oVqpx3lNFXMjs/RC9ZUuhBhcwDM2BwOgoMw+PZ+beqQ==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "bookgen.vfs": { diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json index 5797d95f..012f2179 100644 --- a/Source/BookGen.Shell.Shared/packages.lock.json +++ b/Source/BookGen.Shell.Shared/packages.lock.json @@ -4,11 +4,11 @@ "net10.0": { "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "FU/IfjDfwaMuKr414SSQNTIti/69bHEMb+QKrskRb26oVqpx3lNFXMjs/RC9ZUuhBhcwDM2BwOgoMw+PZ+beqQ==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "Webmaster442.WindowsTerminal": { @@ -19,9 +19,9 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "L3AdmZ1WOK4XXT5YFPEwyt0ep6l8lGIPs7F5OOBZc77Zqeo01Of7XXICy47628sdVl0v/owxYJTe86DTgFwKCA==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" } } } diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json index e8d77060..5cdb23de 100644 --- a/Source/BookGen.Shellprog/packages.lock.json +++ b/Source/BookGen.Shellprog/packages.lock.json @@ -4,26 +4,26 @@ "net10.0": { "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "BStFkd5CcnEtarlcgYDBcFzGYCuuNMzPs02wN3WBsOFoYIEmYoUdAiU+au6opzoqfTYJsMTW00AeqDdnXH2CvA==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.0", - "Microsoft.Extensions.Logging.Abstractions": "10.0.0", - "Microsoft.Extensions.Options": "10.0.0" + "Microsoft.Extensions.DependencyInjection": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "treWetuksp8LVb09fCJ5zNhNJjyDkqzVm83XxcrlWQnAdXznR140UUXo8PyEPBvFlHhjKhFQZEOP3Sk/ByCvEw==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "38Q8sEHwQ/+wVO/mwQBa0fcdHbezFpusHE+vBw/dSr6Fq/kzZm3H/NQX511Jki/R3FHd64IY559gdlHZQtYeEA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", - "Microsoft.Extensions.Logging": "10.0.0", - "Microsoft.Extensions.Logging.Abstractions": "10.0.0", - "Microsoft.Extensions.Logging.Configuration": "10.0.0", - "Microsoft.Extensions.Options": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Logging": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Logging.Configuration": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" } }, "Spectre.Console": { @@ -40,85 +40,85 @@ }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "H4SWETCh/cC5L1WtWchHR6LntGk3rDTTznZMssr4cL8IbDmMWBxY+MOGDc/ASnqNolLKPIWHWeuC1ddiL/iNPw==", + "resolved": "10.0.1", + "contentHash": "njoRekyMIK+smav8B6KL2YgIfUtlsRNuT7wvurpLW+m/hoRKVnoELk2YxnUnWRGScCd1rukLMxShwLqEOKowDg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.0", - "Microsoft.Extensions.Primitives": "10.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "d2kDKnCsJvY7mBVhcjPSp9BkJk48DsaHPg5u+Oy4f8XaOqnEedRy/USyvnpHL92wpJ6DrTPy7htppUUzskbCXQ==", + "resolved": "10.0.1", + "contentHash": "kPlU11hql+L9RjrN2N9/0GcRcRcZrNFlLLjadasFWeBORT6pL6OE+RYRk90GGCyVGSxTK+e1/f3dsMj5zpFFiQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.0" + "Microsoft.Extensions.Primitives": "10.0.1" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "tMF9wNh+hlyYDWB8mrFCQHQmWHlRosol1b/N2Jrefy1bFLnuTlgSYmPyHNmz8xVQgs7DpXytBRWxGhG+mSTp0g==", + "resolved": "10.0.1", + "contentHash": "Lp4CZIuTVXtlvkAnTq6QvMSW7+H62gX2cU2vdFxHQUxvrWTpi7LwYI3X+YAyIS0r12/p7gaosco7efIxL4yFNw==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.0" + "Microsoft.Extensions.Configuration": "10.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.1" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "f0RBabswJq+gRu5a+hWIobrLWiUYPKMhCD9WO3sYBAdSy3FFH14LMvLVFZc2kPSCimBLxSuitUhsd6tb0TAY6A==", + "resolved": "10.0.1", + "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "j8zcwhS6bYB6FEfaY3nYSgHdpiL2T+/V3xjpHtslVAegyI1JUbB9yAt/BFdvZdsNbY0Udm4xFtvfT/hUwcOOOg==", + "resolved": "10.0.1", + "contentHash": "Zg8LLnfZs5o2RCHD/+9NfDtJ40swauemwCa7sI8gQoAye/UJHRZNpCtC7a5XE7l9Z7mdI8iMWnLZ6m7Q6S3jLg==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.0", - "Microsoft.Extensions.Configuration.Binder": "10.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", - "Microsoft.Extensions.Logging": "10.0.0", - "Microsoft.Extensions.Logging.Abstractions": "10.0.0", - "Microsoft.Extensions.Options": "10.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.0" + "Microsoft.Extensions.Configuration": "10.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", + "Microsoft.Extensions.Configuration.Binder": "10.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Logging": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.1" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "8oCAgXOow5XDrY9HaXX1QmH3ORsyZO/ANVHBlhLyCeWTH5Sg4UuqZeOTWJi6484M+LqSx0RqQXDJtdYy2BNiLQ==", + "resolved": "10.0.1", + "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", - "Microsoft.Extensions.Primitives": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "tL9cSl3maS5FPzp/3MtlZI21ExWhni0nnUCF8HY4npTsINw45n9SNDbkKXBMtFyUFGSsQep25fHIDN4f/Vp3AQ==", + "resolved": "10.0.1", + "contentHash": "pL78/Im7O3WmxHzlKUsWTYchKL881udU7E26gCD3T0+/tPhWVfjPwMzfN/MRKU7aoFYcOiqcG2k1QTlH5woWow==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.0", - "Microsoft.Extensions.Configuration.Binder": "10.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", - "Microsoft.Extensions.Options": "10.0.0", - "Microsoft.Extensions.Primitives": "10.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", + "Microsoft.Extensions.Configuration.Binder": "10.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "inRnbpCS0nwO/RuoZIAqxQUuyjaknOOnCEZB55KSMMjRhl0RQDttSmLSGsUJN3RQ3ocf5NDLFd2mOQViHqMK5w==" + "resolved": "10.0.1", + "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" }, "bookgen.cli": { "type": "Project", "dependencies": { "BookGen.Vfs": "[1.0.0, )", - "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.0, )", - "Microsoft.Extensions.Logging.Abstractions": "[10.0.0, )" + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )" } }, "bookgen.contents": { @@ -127,7 +127,7 @@ "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[10.0.0, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -136,17 +136,17 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "L3AdmZ1WOK4XXT5YFPEwyt0ep6l8lGIPs7F5OOBZc77Zqeo01Of7XXICy47628sdVl0v/owxYJTe86DTgFwKCA==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "FU/IfjDfwaMuKr414SSQNTIti/69bHEMb+QKrskRb26oVqpx3lNFXMjs/RC9ZUuhBhcwDM2BwOgoMw+PZ+beqQ==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "Webmaster442.WindowsTerminal": { diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index c61bcf56..1329cd17 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -4,41 +4,41 @@ "net10.0": { "Markdig": { "type": "Direct", - "requested": "[0.43.0, )", - "resolved": "0.43.0", - "contentHash": "tbP3Y/GYC5pIUWXw6Iqf+R5CwBGA4VJswnlgS2x3TgcHoH4IlnGVmL7hfA1sXkZoCPm/4mhzS8cJUkWhQthaPg==" + "requested": "[0.44.0, )", + "resolved": "0.44.0", + "contentHash": "X+CYMjcUnh/yO24wOSQxVFLiGqWrrtXJ5M7toHiM1Zk4Fg9UMLN5fkaq6FSOWH+mIprsHHgDMlq3MJhmrXalhg==" }, "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "BStFkd5CcnEtarlcgYDBcFzGYCuuNMzPs02wN3WBsOFoYIEmYoUdAiU+au6opzoqfTYJsMTW00AeqDdnXH2CvA==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.0", - "Microsoft.Extensions.Logging.Abstractions": "10.0.0", - "Microsoft.Extensions.Options": "10.0.0" + "Microsoft.Extensions.DependencyInjection": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "FU/IfjDfwaMuKr414SSQNTIti/69bHEMb+QKrskRb26oVqpx3lNFXMjs/RC9ZUuhBhcwDM2BwOgoMw+PZ+beqQ==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "treWetuksp8LVb09fCJ5zNhNJjyDkqzVm83XxcrlWQnAdXznR140UUXo8PyEPBvFlHhjKhFQZEOP3Sk/ByCvEw==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "38Q8sEHwQ/+wVO/mwQBa0fcdHbezFpusHE+vBw/dSr6Fq/kzZm3H/NQX511Jki/R3FHd64IY559gdlHZQtYeEA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", - "Microsoft.Extensions.Logging": "10.0.0", - "Microsoft.Extensions.Logging.Abstractions": "10.0.0", - "Microsoft.Extensions.Logging.Configuration": "10.0.0", - "Microsoft.Extensions.Options": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Logging": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Logging.Configuration": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" } }, "Microsoft.IO.RecyclableMemoryStream": { @@ -113,78 +113,78 @@ }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "H4SWETCh/cC5L1WtWchHR6LntGk3rDTTznZMssr4cL8IbDmMWBxY+MOGDc/ASnqNolLKPIWHWeuC1ddiL/iNPw==", + "resolved": "10.0.1", + "contentHash": "njoRekyMIK+smav8B6KL2YgIfUtlsRNuT7wvurpLW+m/hoRKVnoELk2YxnUnWRGScCd1rukLMxShwLqEOKowDg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.0", - "Microsoft.Extensions.Primitives": "10.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "d2kDKnCsJvY7mBVhcjPSp9BkJk48DsaHPg5u+Oy4f8XaOqnEedRy/USyvnpHL92wpJ6DrTPy7htppUUzskbCXQ==", + "resolved": "10.0.1", + "contentHash": "kPlU11hql+L9RjrN2N9/0GcRcRcZrNFlLLjadasFWeBORT6pL6OE+RYRk90GGCyVGSxTK+e1/f3dsMj5zpFFiQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.0" + "Microsoft.Extensions.Primitives": "10.0.1" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "tMF9wNh+hlyYDWB8mrFCQHQmWHlRosol1b/N2Jrefy1bFLnuTlgSYmPyHNmz8xVQgs7DpXytBRWxGhG+mSTp0g==", + "resolved": "10.0.1", + "contentHash": "Lp4CZIuTVXtlvkAnTq6QvMSW7+H62gX2cU2vdFxHQUxvrWTpi7LwYI3X+YAyIS0r12/p7gaosco7efIxL4yFNw==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.0" + "Microsoft.Extensions.Configuration": "10.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.1" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "f0RBabswJq+gRu5a+hWIobrLWiUYPKMhCD9WO3sYBAdSy3FFH14LMvLVFZc2kPSCimBLxSuitUhsd6tb0TAY6A==", + "resolved": "10.0.1", + "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "j8zcwhS6bYB6FEfaY3nYSgHdpiL2T+/V3xjpHtslVAegyI1JUbB9yAt/BFdvZdsNbY0Udm4xFtvfT/hUwcOOOg==", + "resolved": "10.0.1", + "contentHash": "Zg8LLnfZs5o2RCHD/+9NfDtJ40swauemwCa7sI8gQoAye/UJHRZNpCtC7a5XE7l9Z7mdI8iMWnLZ6m7Q6S3jLg==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.0", - "Microsoft.Extensions.Configuration.Binder": "10.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", - "Microsoft.Extensions.Logging": "10.0.0", - "Microsoft.Extensions.Logging.Abstractions": "10.0.0", - "Microsoft.Extensions.Options": "10.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.0" + "Microsoft.Extensions.Configuration": "10.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", + "Microsoft.Extensions.Configuration.Binder": "10.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Logging": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.1" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "8oCAgXOow5XDrY9HaXX1QmH3ORsyZO/ANVHBlhLyCeWTH5Sg4UuqZeOTWJi6484M+LqSx0RqQXDJtdYy2BNiLQ==", + "resolved": "10.0.1", + "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", - "Microsoft.Extensions.Primitives": "10.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "tL9cSl3maS5FPzp/3MtlZI21ExWhni0nnUCF8HY4npTsINw45n9SNDbkKXBMtFyUFGSsQep25fHIDN4f/Vp3AQ==", + "resolved": "10.0.1", + "contentHash": "pL78/Im7O3WmxHzlKUsWTYchKL881udU7E26gCD3T0+/tPhWVfjPwMzfN/MRKU7aoFYcOiqcG2k1QTlH5woWow==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.0", - "Microsoft.Extensions.Configuration.Binder": "10.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", - "Microsoft.Extensions.Options": "10.0.0", - "Microsoft.Extensions.Primitives": "10.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", + "Microsoft.Extensions.Configuration.Binder": "10.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.0", - "contentHash": "inRnbpCS0nwO/RuoZIAqxQUuyjaknOOnCEZB55KSMMjRhl0RQDttSmLSGsUJN3RQ3ocf5NDLFd2mOQViHqMK5w==" + "resolved": "10.0.1", + "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" }, "Newtonsoft.Json": { "type": "Transitive", @@ -222,8 +222,8 @@ "type": "Project", "dependencies": { "BookGen.Vfs": "[1.0.0, )", - "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.0, )", - "Microsoft.Extensions.Logging.Abstractions": "[10.0.0, )" + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )" } }, "bookgen.contents": { @@ -234,7 +234,7 @@ "dependencies": { "BookGen.Shell.Shared": "[1.0.0, )", "BookGen.Vfs": "[1.0.0, )", - "Markdig": "[0.43.0, )", + "Markdig": "[0.44.0, )", "Microsoft.ClearScript": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.linux-x64": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.win-x64": "[7.5.0, )", @@ -242,14 +242,14 @@ "SkiaSharp.NativeAssets.Linux": "[3.119.1, )", "SkiaSharp.NativeAssets.Win32": "[3.119.1, )", "Svg.Skia": "[3.2.1, )", - "System.ServiceModel.Syndication": "[10.0.0, )", + "System.ServiceModel.Syndication": "[10.0.1, )", "YamlDotNet": "[16.3.0, )" } }, "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[10.0.0, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -283,9 +283,9 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "L3AdmZ1WOK4XXT5YFPEwyt0ep6l8lGIPs7F5OOBZc77Zqeo01Of7XXICy47628sdVl0v/owxYJTe86DTgFwKCA==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" }, "SkiaSharp": { "type": "CentralTransitive", @@ -322,9 +322,9 @@ }, "System.ServiceModel.Syndication": { "type": "CentralTransitive", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "HBrGaOQoEaDlMOykS/LfQr9BZ0pEF4SOcscH5oMpBF46+lqMMfgfat3Jm5SpW8IaDzzn/IakYjpWk3bEGSh1vA==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "Z3+s66hOp7JVDOhkVk0gH/sjsshn99NUMiILcXLaA+3qe2OkLr3nB+3bRzXdfF6arlJBxGfkkki9GP6R9kvdFg==" }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index 7391fac1..f155489c 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -4,9 +4,9 @@ "net10.0": { "Markdig": { "type": "Direct", - "requested": "[0.43.0, )", - "resolved": "0.43.0", - "contentHash": "tbP3Y/GYC5pIUWXw6Iqf+R5CwBGA4VJswnlgS2x3TgcHoH4IlnGVmL7hfA1sXkZoCPm/4mhzS8cJUkWhQthaPg==" + "requested": "[0.44.0, )", + "resolved": "0.44.0", + "contentHash": "X+CYMjcUnh/yO24wOSQxVFLiGqWrrtXJ5M7toHiM1Zk4Fg9UMLN5fkaq6FSOWH+mIprsHHgDMlq3MJhmrXalhg==" }, "Microsoft.ClearScript": { "type": "Direct", @@ -68,9 +68,9 @@ }, "System.ServiceModel.Syndication": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "HBrGaOQoEaDlMOykS/LfQr9BZ0pEF4SOcscH5oMpBF46+lqMMfgfat3Jm5SpW8IaDzzn/IakYjpWk3bEGSh1vA==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "Z3+s66hOp7JVDOhkVk0gH/sjsshn99NUMiILcXLaA+3qe2OkLr3nB+3bRzXdfF6arlJBxGfkkki9GP6R9kvdFg==" }, "YamlDotNet": { "type": "Direct", From a63f731921fe07c7f1c65f4887176898abf3c00f Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sat, 27 Dec 2025 19:03:51 +0100 Subject: [PATCH 05/41] Md2Terminal --- Source/BookGen.Vfs/Extensions.cs | 109 +++++++++++------- Source/BookGen/Commands/Md2HtmlCommand.cs | 24 +--- Source/BookGen/Commands/Md2TerminalCommand.cs | 65 +++++++++++ 3 files changed, 135 insertions(+), 63 deletions(-) create mode 100644 Source/BookGen/Commands/Md2TerminalCommand.cs diff --git a/Source/BookGen.Vfs/Extensions.cs b/Source/BookGen.Vfs/Extensions.cs index f9ff6546..f5b767af 100644 --- a/Source/BookGen.Vfs/Extensions.cs +++ b/Source/BookGen.Vfs/Extensions.cs @@ -3,6 +3,7 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Schema; @@ -12,60 +13,86 @@ namespace BookGen.Vfs; public static class Extensions { - public static XmlWriter CreateXmlWriter(this IWritableFileSystem fs, string path) + extension(IReadOnlyFileSystem fs) { - return XmlWriter.Create(fs.CreateTextWriter(path)); - } - - public static async Task DeserializeAsync(this IReadOnlyFileSystem fs, string path) - { - await using var stream = fs.OpenReadStream(path); - T? result = await JsonSerializer.DeserializeAsync(stream, JsonOptions.SerializerOptions); - return result; - } - - public static async Task SerializeAsync(this IWritableFileSystem fs, string path, T value, bool writeSchema) - { - await using var stream = fs.CreateWriteStream(path); - await JsonSerializer.SerializeAsync(stream, value, JsonOptions.SerializerOptions); - if (writeSchema) + public async Task DeserializeAsync(string path) { - var newName = Path.ChangeExtension(path, ".schema.json"); - await fs.WriteSchema(newName); + await using var stream = fs.OpenReadStream(path); + T? result = await JsonSerializer.DeserializeAsync(stream, JsonOptions.SerializerOptions); + return result; } - } - public static async Task WriteJsonAsync(this IWritableFileSystem fs, string path, JsonObject json) - { - await fs.WriteAllTextAsync(path, json.ToJsonString(JsonOptions.SerializerOptions)); - } + public async Task ReadJsonAsync(string path) + { + string content = await fs.ReadAllTextAsync(path); + JsonNode? parsed = JsonNode.Parse(content); + if (parsed is not JsonObject jsonObject) + { + throw new InvalidOperationException($"Failed to parse JSON from {path}"); + } + return jsonObject; + } - public static async Task ReadJsonAsync(this IReadOnlyFileSystem fs, string path) - { - string content = await fs.ReadAllTextAsync(path); - var parsed = JsonObject.Parse(content); - if (parsed is not JsonObject jsonObject) + public string GetFileNameInTargetFolder(IReadOnlyFileSystem targetFolder, string file, string newExtension) { - throw new InvalidOperationException($"Failed to parse JSON from {path}"); + ArgumentException.ThrowIfNullOrWhiteSpace(fs.Scope); + ArgumentException.ThrowIfNullOrWhiteSpace(targetFolder.Scope); + + var fullPath = Path.GetFullPath(file, fs.Scope); + + var relativePart = Path.GetRelativePath(fs.Scope, fullPath); + + return Path.ChangeExtension(Path.GetFullPath(relativePart, targetFolder.Scope), newExtension); } - return jsonObject; - } - public static async Task WriteSchema(this IWritableFileSystem fs, string path) - { - var node = JsonOptions.SerializerOptions.GetJsonSchemaAsNode(typeof(T), JsonOptions.ExporterOptions); - await fs.WriteAllTextAsync(path, node.ToJsonString(JsonOptions.SerializerOptions)); + public (string content, DateTime lastmodified) ReadInputFiles(string[] inputFiles) + { + StringBuilder md = new(inputFiles.Length * 1024); + DateTime lastmodified = DateTime.MinValue; + foreach (var inputFile in inputFiles) + { + string content = fs.ReadAllText(inputFile); + DateTime date = fs.GetLastModifiedUtc(inputFile); + + if (date > lastmodified) + lastmodified = date; + + md.Append(content); + + if (!content.EndsWith('\n')) + md.Append(System.Environment.NewLine); + } + return (md.ToString(), lastmodified); + } } - public static string GetFileNameInTargetFolder(this IReadOnlyFileSystem sourceFolder, IReadOnlyFileSystem targetFolder, string file, string newExtension) + extension(IWritableFileSystem fs) { - ArgumentException.ThrowIfNullOrWhiteSpace(sourceFolder.Scope); - ArgumentException.ThrowIfNullOrWhiteSpace(targetFolder.Scope); + public XmlWriter CreateXmlWriter(string path) + { + return XmlWriter.Create(fs.CreateTextWriter(path)); + } - var fullPath = Path.GetFullPath(file, sourceFolder.Scope); + public async Task WriteJsonAsync(string path, JsonObject json) + { + await fs.WriteAllTextAsync(path, json.ToJsonString(JsonOptions.SerializerOptions)); + } - var relativePart = Path.GetRelativePath(sourceFolder.Scope, fullPath); + public async Task WriteSchema(string path) + { + var node = JsonOptions.SerializerOptions.GetJsonSchemaAsNode(typeof(T), JsonOptions.ExporterOptions); + await fs.WriteAllTextAsync(path, node.ToJsonString(JsonOptions.SerializerOptions)); + } - return Path.ChangeExtension(Path.GetFullPath(relativePart, targetFolder.Scope), newExtension); + public async Task SerializeAsync(string path, T value, bool writeSchema) + { + await using var stream = fs.CreateWriteStream(path); + await JsonSerializer.SerializeAsync(stream, value, JsonOptions.SerializerOptions); + if (writeSchema) + { + var newName = Path.ChangeExtension(path, ".schema.json"); + await fs.WriteSchema(newName); + } + } } } diff --git a/Source/BookGen/Commands/Md2HtmlCommand.cs b/Source/BookGen/Commands/Md2HtmlCommand.cs index 6c2f39ad..e4965f1b 100644 --- a/Source/BookGen/Commands/Md2HtmlCommand.cs +++ b/Source/BookGen/Commands/Md2HtmlCommand.cs @@ -71,7 +71,7 @@ public override ValidationResult Validate(IValidationContext context) if (string.IsNullOrEmpty(OutputFile)) result.AddIssue("Output file must be specified"); - if (!InputFiles.Any()) + if (InputFiles.Length == 0) result.AddIssue("An Input file must be specified"); foreach (var inputfile in InputFiles) @@ -105,7 +105,7 @@ public Md2HtmlCommand(ILogger log, IWritableFileSystem fileSystem, IAssetSource public override int Execute(Md2HtmlArguments arguments, IReadOnlyList context) { - (string md, DateTime lastmodified) = ReadInputFiles(arguments.InputFiles); + (string md, DateTime lastmodified) = _fileSystem.ReadInputFiles(arguments.InputFiles); string? pageTemplate = string.Empty; @@ -163,26 +163,6 @@ public override int Execute(Md2HtmlArguments arguments, IReadOnlyList co return ExitCodes.Success; } - private (string content, DateTime lastmodified) ReadInputFiles(string[] inputFiles) - { - StringBuilder md = new(inputFiles.Length * 1024); - DateTime lastmodified = DateTime.MinValue; - foreach (var inputFile in inputFiles) - { - string content = _fileSystem.ReadAllText(inputFile); - DateTime date = _fileSystem.GetLastModifiedUtc(inputFile); - - if (date > lastmodified) - lastmodified = date; - - md.Append(content); - - if (!content.EndsWith('\n')) - md.Append(System.Environment.NewLine); - } - return (md.ToString(), lastmodified); - } - private bool ValidateTemplate(string pageTemplate) { bool returnValue = true; diff --git a/Source/BookGen/Commands/Md2TerminalCommand.cs b/Source/BookGen/Commands/Md2TerminalCommand.cs new file mode 100644 index 00000000..d59d337c --- /dev/null +++ b/Source/BookGen/Commands/Md2TerminalCommand.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2025 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Bookgen.Lib.Templates; + +using BookGen.Cli; +using BookGen.Cli.Annotations; +using BookGen.Vfs; + +using Microsoft.Extensions.Logging; + +namespace BookGen.Commands; + +internal sealed class Md2TerminalCommand : Command +{ + private readonly ILogger _log; + private readonly IWritableFileSystem _fileSystem; + + internal sealed class Arguments : ArgumentsBase + { + [Switch("i", "input")] + public string[] InputFiles { get; set; } + + [Switch("o", "output")] + public string OutputFile { get; set; } + + public Arguments() + { + InputFiles = []; + OutputFile = string.Empty; + } + + public override ValidationResult Validate(IValidationContext context) + { + ValidationResult result = new(); + + if (string.IsNullOrEmpty(OutputFile)) + result.AddIssue("Output file must be specified"); + + if (InputFiles.Length == 0) + result.AddIssue("An Input file must be specified"); + + foreach (var inputfile in InputFiles) + { + if (!context.FileSystem.FileExists(inputfile)) + result.AddIssue($"Input file: {inputfile} doesn't exist"); + } + + return base.Validate(context); + } + } + + public Md2TerminalCommand(ILogger log, IWritableFileSystem fileSystem) + { + _log = log; + _fileSystem = fileSystem; + } + + public override int Execute(Arguments arguments, IReadOnlyList context) + { + (string md, DateTime lastmodified) = _fileSystem.ReadInputFiles(arguments.InputFiles); + } +} From 891bf23805d19b891b12e56642f62abd4bdd0f66 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sat, 27 Dec 2025 20:30:49 +0100 Subject: [PATCH 06/41] Md2terminal command --- Commands.md | 703 ++++++++++-------- Source/BookGen/Commands/Md2HtmlCommand.cs | 4 +- Source/BookGen/Commands/Md2TerminalCommand.cs | 34 +- 3 files changed, 446 insertions(+), 295 deletions(-) diff --git a/Commands.md b/Commands.md index 72748c9f..03855b09 100644 --- a/Commands.md +++ b/Commands.md @@ -2,8 +2,7 @@ BookGen - Markdown to Book tool. -For the tool to work in the work folder there must be a bookgen.json config file. This config file -can be created with the following command: +For the tool to work in the work folder there must be a bookgen.json config file. This config file can be created with the following command: `BookGen Newbook` @@ -17,500 +16,604 @@ To list available subcommands type: General arguments: -`-wd` -`--wait-debugger` - Waits for a debugger to be attached. Usefull for error reporting & error finding. +* `-wd` or `--wait-debugger` -`-ad` -`--attach-debugger` - Attaches a debugger. Usefull for error reporting & error finding. + Waits for a debugger to be attached. Usefull for error reporting & error finding. -`-js` -`--json-log` - Outputs log in JSON format. Usefull for interop purposes. +* `-ad` or `--attach-debugger` + + Attaches a debugger. Usefull for error reporting & error finding. + +* `-js` or `--json-log` + + Outputs log in JSON format. Usefull for interop purposes. # Addfrontmatter Add a basic YAML frontmatter information to all markdown files located in the current folder and it's subfolders. -`BookGen Addfrontmatter [-v] [-d [directory]]` -`BookGen Addfrontmatter [--verbose] [--dir [directory]]` +``` +BookGen Addfrontmatter [-v] [-d [directory]] +BookGen Addfrontmatter [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then - the current directory will be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues # Assembly-document Generates a markdown file(s) from a given .NET assembly and it's XML documentation file. -`BookGen Assembly-document -i -o [-d] [-n]` -`BookGen Assembly-document --input --output [--dry] [--namespace-pages]` +``` +BookGen Assembly-document -i -o [-d] [-n] +BookGen Assembly-document --input --output [--dry] [--namespace-pages] +``` Arguments: --i, --input: - Required argument. Specifies the input assembly file path. The file must be a .NET assembly. +* `-i`, `--input`: + + Required argument. Specifies the input assembly file path. The file must be a .NET assembly. + +* `-o`, `--output`: + + Required argument. Specifies the output files path. + +* `-d`, `--dry`: --o, --output: - Required argument. Specifies the output files path. + Optional argument. If specified, the command will not write any files, but will only print the output to console. --d, --dry: - Optional argument. If specified, the command will not write any files, - but will only print the output to console. +* `-n`, `--namespace-pages`: --n, --namespace-pages: - Optional argument. If specified, the command will create a separate markdown file for each - namespace in the assembly. + Optional argument. If specified, the command will create a separate markdown file for each namespace in the assembly. # BuildEpub Build an epub3 file from the book. -`BookGen BuildEpub -o [-v] [-d [directory]]` -`BookGen BuildEpub --output [--verbose] [--dir [directory]]` +``` +BookGen BuildEpub -o [-v] [-d [directory]] +BookGen BuildEpub --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues # BuildExport Build a JSON file with schema for post processing of the book. -`BookGen BuildExport -o [-v] [-d [directory]]` -`BookGen BuildExport --output [--verbose] [--dir [directory]]` +``` +BookGen BuildExport -o [-v] [-d [directory]] +BookGen BuildExport --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: --h, --host: - Optional argument. If specified, the host name set in the config file will be ignored and the - host name will be set to the specified value. + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-h`, `--host`: + + Optional argument. If specified, the host name set in the config file will be ignored and the host name will be set to the specified value. # BuildFeed Build an RSS 2.0 and an Atom 1.0 feed from the book. -`BookGen BuildFeed -o [-v] [-d [directory]]` -`BookGen BuildFeed --output [--verbose] [--dir [directory]]` +``` +BookGen BuildFeed -o [-v] [-d [directory]] +BookGen BuildFeed --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # BuildPrint Build a printable html & xhtml file from the book -`BookGen BuildPrint -o [-v] [-d [directory]]` -`BookGen BuildPrint --output [--verbose] [--dir [directory]]` +``` +BookGen BuildPrint -o [-v] [-d [directory]] +BookGen BuildPrint --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --h, --host: - Optional argument. If specified, the host name set in the config file will be ignored and the - host name will be set to the specified value. +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-h`, `--host`: + + Optional argument. If specified, the host name set in the config file will be ignored and the host name will be set to the specified value. # BuildWeb Build a static website from the book -`BookGen BuildWeb -o [-v] [-d [directory]]` -`BookGen BuildWeb --output [--verbose] [--dir [directory]]` +``` +BookGen BuildWeb -o [-v] [-d [directory]] +BookGen BuildWeb --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues --h, --host: - Optional argument. If specified, the host name set in the config file will be ignored and the - host name will be set to the specified value. +* `-h`, `--host`: + + Optional argument. If specified, the host name set in the config file will be ignored and the host name will be set to the specified value. # BuildWp Build a wordpress export file from the book. -`BookGen BuildWp -o [-v] [-d [directory]]` -`BookGen BuildWp --output [--verbose] [--dir [directory]]` +``` +BookGen BuildWp -o [-v] [-d [directory]] +BookGen BuildWp --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: + + Required argument. Specifies the output directory name. --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues --h, --host: - Optional argument. If specified, the host name set in the config file will be ignored and the - host name will be set to the specified value. +* `-h`, `--host`: + + + Optional argument. If specified, the host name set in the config file will be ignored and the host name will be set to the specified value. # Config Get or set bookgen application specific settings -`BookGen Config` - List all currently supported application wide settings +* `BookGen Config` + + List all currently supported application wide settings + +* `BookGen Config ` -`BookGen Config ` - Gets a setting value, prints it to output and exits. + Gets a setting value, prints it to output and exits. -`BookGen Config ` - Sets a setting value and exits +* `BookGen Config ` + + Sets a setting value and exits # Edit Open a file for editing with configured editor. +`BookGen edit [filename]` + # Gui Starts the program with a command line gui interface - -`BookGen Gui [-v] [-d [directory]]` -`BookGen Gui [--verbose] [--dir [directory]]` +``` +BookGen Gui [-v] [-d [directory]] +BookGen Gui [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. -`BookGen edit [filename]` +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues. # Html2Pdf Converts a HTML file to a png using edges or chromes headless mode. The tool will use chrome, if it's installed, otherwise it will use edge. This command is only supported on Windows OS. -`BookGen Html2Pdf -i -o ` -`BookGen Html2Pdf --input --output ` +``` +BookGen Html2Pdf -i -o +BookGen Html2Pdf --input --output +``` --i, --input: - Input html file with extension of .htm or .html +* `-i`, `--input`: --o, --output: - Output PDF file. + Input html file with extension of .htm or .html + +* `-o`, `--output`: + + Output PDF file. # Html2Png Converts a HTML file to a png using edges or chromes headless mode. The tool will use chrome, if it's installed, otherwise it will use edge. This command is only supported on Windows OS. -`BookGen Html2Png -i -o [-w [width]] [-h [height]]` -`BookGen Html2Png --input --output [--width [width]] [--height [height]]` +``` +BookGen Html2Png -i -o [-w [width]] [-h [height]] +BookGen Html2Png --input --output [--width [width]] [--height [height]] +``` + +* `-i`, `--input`: + + Input html file with extension of .htm or .html --i, --input: - Input html file with extension of .htm or .html +* `-o`, `--output`: --o, --output: - Output PNG file. + Output PNG file. --w, --width: - Optional argument. Specifies the width of the output image in pixels. +* `-w`, `--width`: --h, --height: - Optional argument. Specifies the height of the output image in pixels. + Optional argument. Specifies the width of the output image in pixels. + +* `-h`, `--height`: + + Optional argument. Specifies the height of the output image in pixels. # ImgConvert Converts an image file to a different format. The tool supports png, jpeg, webp and svg formats. -`BookGen ImgConvert -i -o -f [-q [quality]] [-r [resolution]]` +``` +BookGen ImgConvert -i -o -f [-q [quality]] [-r [resolution]] +``` Arguments: --i, --input: - Required argument. Specifies the input image file path. The file must be a valid image file or - a directory containing image files. +* `-i`, `--input`: + + Required argument. Specifies the input image file path. The file must be a valid image file or a directory containing image files. --o, --output: - Required argument. Specifies the output file path. The file must have a valid image file - extension, like .png, .jpg, .jpeg, .webp. Can also be a directory, in which case the output - files will be saved with the same name as the input files +* `-o`, `--output`: + + Required argument. Specifies the output file path. The file must have a valid image file extension, like .png, .jpg, .jpeg, .webp. Can also be a directory, in which case the output files will be saved with the same name as the input files + +* `-f`, `--format`: --f, --format: Required argument. Specifies the output image format. Supported formats are png, jpeg, webp. --q, --quality: - Optional argument. Specifies the quality of the output image. The value must be between - 0 and 100. Default is 90. If not specified, then the default value will be used. +* `-q`, `--quality`: + + Optional argument. Specifies the quality of the output image. The value must be between 0 and 100. Default is 90. If not specified, then the default value will be used. + +* `-r`, `--resolution`: --r, --resolution: - Optional argument. Specifies the resolution of the output image. The value must be a valid - resolution string, like 1920x1080 or 1280x720. If not specified, then the resolution will be - the same as the input image. + Optional argument. Specifies the resolution of the output image. The value must be a valid resolution string, like 1920x1080 or 1280x720. If not specified, then the resolution will be the same as the input image. # Install Windows only command that installs BookGen to the system PATH & optionally to the windows terminal. -`BookGen Install` +``` +BookGen Install +``` # JsonArgs Creates an empty json arguments template file for a given bookgen command. -`BookGen JsonArgs -c [-d [directory]]` -`BookGen JsonArgs --command [--dir [directory]]` +``` +BookGen JsonArgs -c [-d [directory]] +BookGen JsonArgs --command [--dir [directory]] +``` -A Json arguments template can be used to store command line arguments, so the bookgen command can -be invoked with the same arguments without having to type them in again. +A Json arguments template can be used to store command line arguments, so the bookgen command can be invoked with the same arguments without having to type them in again. Arguments: --c, --command: - Required argument. Specifies the command for which the json template will be created. +* `-c`, `--command`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the command for which the json template will be created. + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. # Links Scans all markdown files in the current book and writes the lins to a markdown file, named links.md -`BookGen Links [-v] [-d [directory]]` -`BookGen Links [--verbose] [--dir [directory]]` +``` +BookGen Links [-v] [-d [directory]] +BookGen Links [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # Math2Svg Renders a single markdown file containing Tex formulas to svg files -`BookGen Math2Svg -f -o [-s [scale]]` -`BookGen Math2Svg --formula --output [--scale [scale]]` +``` +BookGen Math2Svg -f -o [-s [scale]] +BookGen Math2Svg --formula --output [--scale [scale]] +``` Arguments: --f, --formula: - Formula to render to svg. The formula must be a valid Tex formula. +* `-f`, `--formula`: + + Formula to render to svg. The formula must be a valid Tex formula. + +* `-o`, `--output`: --o, --output: - Output svg file. + Output svg file. --s, --scale: - Optional argument. Specifies the scale of the output svg file. Default is 1.0. - If not specified, then the default value will be used. +* `-s`, `--scale`: + + Optional argument. Specifies the scale of the output svg file. Default is 1.0. If not specified, then the default value will be used. # Md2HTML Renders a single markdown file to an HTML file -`BookGen Md2HTML -i -o ` -`BookGen Md2HTML --input --output ` +``` +BookGen Md2HTML -i -o +BookGen Md2HTML --input --output +``` Arguments: --i, --input: - Input markdown file path. Multiple files can be set with multiple - -i arguments +* `-i`, `--input`: + + Input markdown file path. Multiple files can be set with multiple `-i` arguments + +* `-o`, `--output`: + + Output html file path. If file name is "-", outputs to console. + +* `-tf`, `--template` + + Optional argument. If not specified, default template is used. If custom file provided, then the file must contain the folloing tags: + + * `` - For document title + * `` - For document content + +* `-ns`, `--no-syntax` + + Optional argument. Disables syntax highlighting. + +* `-ne`, `--no-embed` + + Optional argument. Disables embedding of media site links, like youtube. --o, --output: - Output html file path. If file name is "con", outputs to console. +* `-r`, `--raw` --tf, --template - Optional argument. If not specified, default template is used. If custom file provided, then - the file must contain the folloing tags: - `` - For document title - `` - For document content + Optional argument. Disables full html generation, only outputs the html produced by the markdown formatting. --ns, --no-syntax - Optional argument. Disables syntax highlighting. +* `-s`, `--svg` --ne, --no-embed - Optional argument. Disables embedding of media site links, - like youtube. + Enables SVG Passthrough. When enabled SVG files will be embedded in resulting html, instead of being rendered to webp. --r, --raw - Optional argument. Disables full html generation, only outputs the html produced by - the markdown formatting. +* `-t`, `--title` --s, --svg - Enables SVG Passthrough. When enabled SVG files will be embedded in resulting html, instead - of being rendered to webp. + Optional argument. Specifies the rendered HTML page title. Only has affect, when `-r` or `--raw` is not specified. --t, --title - Optional argument. Specifies the rendered HTML page title. Only has affect, when -r or --raw - is not specified. +# Md2terminal + +Converts a markdown file to terminal formatted text. + +``` +BookGen Md2terminal -i -o +BookGen Md2terminal --input --output +``` + +* `-i`, `--input`: + + Input markdown file path. Multiple files can be set with multiple `-i` arguments + +* `-o`, `--output`: + + Output html file path. If file name is "-", outputs to console. # Migrate Migrate an old Bookgen book to the new format. -`BookGen Migrate [-v] [-d [directory]]` -`BookGen Migrate [--verbose] [--dir [directory]]` +``` +BookGen Migrate [-v] [-d [directory]] +BookGen Migrate [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # NewBook Creates a new book structure in the given folder -`BookGen NewBook [-v] [-d [directory]]` -`BookGen NewBook [--verbose] [-dir [directory]]` +``` +BookGen NewBook [-v] [-d [directory]] +BookGen NewBook [--verbose] [-dir [directory]] +``` Arguments: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. # NewPage Creates a new markdown page. -`BookGen NewPage -n [-v] [-d [directory]]` -`BookGen NewPage --name [--verbose] [-dir [directory]]` +``` +BookGen NewPage -n [-v] [-d [directory]] +BookGen NewPage --name [--verbose] [-dir [directory]] +``` Arguments: --n, --name: - File name. Specifies new file name +* `-n`, `--name`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + File name. Specifies new file name --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. # QrCode Renders an url into a QRCode image -`BookGen QrCode -d -o [-c [color]]` -`BookGen QrCode --data --output [--color [color]]` +``` +BookGen QrCode -d -o [-c [color]] +BookGen QrCode --data --output [--color [color]] +``` Arguments: --d, --data: - Url data to encode. Minimum 1 byte, Maximum 900 bytes +* `-d`, `--data`: + + Url data to encode. Minimum 1 byte, Maximum 900 bytes + +* `-c`, `--color`: --c, --color: - Optional argument. Specifies the color of the QRCode. The color must be a valid hex color code, - like #FF0000 or #F00. + Optional argument. Specifies the color of the QRCode. The color must be a valid hex color code, like #FF0000 or #F00. --o, --output - Output file. Must have .png or .svg extension +* `-o`, `--output` + + Output file. Must have .png or .svg extension # Schemas Creates a schemas.md documentation file, describing the various config schemas used by bookgen. -`BookGen Schemas [-v] [-d [directory]]` -`BookGen Schemas [--verbose] [--dir [directory]]` +``` +BookGen Schemas [-v] [-d [directory]] +BookGen Schemas [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues # Serve Starts a local only http server that serves file from the given directory -`BookGen Serve [-d [directory]]` -`BookGen Serve [--dir [directory]]` +``` +BookGen Serve [-d [directory]] +BookGen Serve [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. # Shell Autocompleter command, that is used by Powershell -`BookGen Shell` +``` +BookGen Shell +``` # Shortcut -Create a cmd file in the current directory that can be used to start the bookgen Shell in the -current directory. +Create a cmd file in the current directory that can be used to start the bookgen Shell in the current directory. -`BookGen Shortcut` +``` +BookGen Shortcut +``` This command is only supported on Windows OS. @@ -518,64 +621,74 @@ This command is only supported on Windows OS. Displays various statistics about the bookgen project. -`BookGen Stats [-v] [-d [directory]]` -`BookGen Stats [--verbose] [--dir [directory]]` +``` +BookGen Stats [-v] [-d [directory]] +BookGen Stats [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # Subcommands Listst all available subcommands -`BookGen SubCommands` +``` +BookGen SubCommands +``` # Templates -`Bookgen Templates [-n [template name]]` -`Bookgen Templates [--name [template name]]` - Lists all available templates, or extracts a single template to the current directory. +``` +Bookgen Templates [-n [template name]] +Bookgen Templates [--name [template name]] +``` + Arguments: --n, --name: - Optional argument. If specified, only the template with the given name will be extracted. - If not specified, all available templates will be printed. +* `-n`, `--name`: + + Optional argument. If specified, only the template with the given name will be extracted. If not specified, all available templates will be printed. # Terminalinstall Installs a bookgen profile to the Windows Termninal. -This command is only supported on Windows OS. +This command is only supported on Windows OS. Without arguments, performs terminal profile install. -`BookGen Terminalinstall [-c] [-t]` -`BookGen Terminalinstall [--checkinstall] [--checkterminalinstall]` +``` +BookGen Terminalinstall [-c] [-t] +BookGen Terminalinstall [--checkinstall] [--checkterminalinstall] +``` Arguments: --c, --checkinstall: - Optional argument. When specified checks, if terminal profile installed or not. If exit code - is 0, profile is installed. +* `-c`, `--checkinstall`: + + Optional argument. When specified checks, if terminal profile installed or not. If exit code is 0, profile is installed. --t, --checkterminalinstall: - Optional argument. When specified checks, if windows terminal is installed or not. If exit code - is 0, terminal is installed. +* `-t`, `--checkterminalinstall`: -Without arguments, performs terminal profile install. + Optional argument. When specified checks, if windows terminal is installed or not. If exit code is 0, terminal is installed. # Tools -Display a list of downloadable tools that can be installed and used with BookGen shell. This -command is only supported on Windows OS. +Display a list of downloadable tools that can be installed and used with BookGen shell. -`BookGen Tools` +This command is only supported on Windows OS. + +``` +BookGen Tools +``` # Upgrade @@ -583,34 +696,40 @@ Upgrades the bookgen project to the latest version. This command will upgrade th config file to the latest version, and will also upgrade the bookgen.toc.json file to the latest version. -`BookGen Upgrade [-v] [-d [directory]]` -`BookGen Upgrade [--verbose] [--dir [directory]]` +``` +BookGen Upgrade [-v] [-d [directory]] +BookGen Upgrade [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then - the current directory will be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # Validate Validate the configuration files used by bookgen in the specified folder. -`BookGen Validate [-v] [-d [directory]]` -`BookGen Validate [--verbose] [--dir [directory]]` +``` +BookGen Validate [-v] [-d [directory]] +BookGen Validate [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues # Version @@ -618,19 +737,21 @@ Print the current program and config API version `BookGen Version` - # Vstasks Generates a Visual Studio Code tasks.json file for the bookgen project. -`BookGen Vstasks [-v] [-d [directory]]` -`BookGen Vstasks [--verbose] [--dir [directory]]` +``` +BookGen Vstasks [-v] [-d [directory]] +BookGen Vstasks [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory - will be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues diff --git a/Source/BookGen/Commands/Md2HtmlCommand.cs b/Source/BookGen/Commands/Md2HtmlCommand.cs index 1761ac0d..f5e2355f 100644 --- a/Source/BookGen/Commands/Md2HtmlCommand.cs +++ b/Source/BookGen/Commands/Md2HtmlCommand.cs @@ -133,9 +133,9 @@ public override int Execute(Md2HtmlArguments arguments, IReadOnlyList co PrismJsInterop = new PrismJsInterop(_assetSource) }; - using var mdToHtml = new MarkdownConverter(settings); + using var markdonwConverter = new MarkdownConverter(settings); - string? mdcontent = mdToHtml.RenderMarkdownToHtml(md); + string? mdcontent = markdonwConverter.RenderMarkdownToHtml(md); string rendered; if (arguments.RawHtml) diff --git a/Source/BookGen/Commands/Md2TerminalCommand.cs b/Source/BookGen/Commands/Md2TerminalCommand.cs index d59d337c..4c20eabd 100644 --- a/Source/BookGen/Commands/Md2TerminalCommand.cs +++ b/Source/BookGen/Commands/Md2TerminalCommand.cs @@ -3,7 +3,10 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- -using Bookgen.Lib.Templates; +using System.Text; + +using Bookgen.Lib.Domain.IO.Configuration; +using Bookgen.Lib.Markdown; using BookGen.Cli; using BookGen.Cli.Annotations; @@ -13,6 +16,7 @@ namespace BookGen.Commands; +[CommandName("md2terminal")] internal sealed class Md2TerminalCommand : Command { private readonly ILogger _log; @@ -58,8 +62,34 @@ public Md2TerminalCommand(ILogger log, IWritableFileSystem fileSystem) _fileSystem = fileSystem; } + private static void WriteToStdout(string rendered) + { + Console.OutputEncoding = Encoding.UTF8; + Spectre.Console.AnsiConsole.WriteLine(rendered); + } + public override int Execute(Arguments arguments, IReadOnlyList context) { - (string md, DateTime lastmodified) = _fileSystem.ReadInputFiles(arguments.InputFiles); + (string md, _) = _fileSystem.ReadInputFiles(arguments.InputFiles); + + using var settings = new RenderSettings(null!) + { + DeleteFirstH1 = false, + AutoEmbedSupportedLinks = false, + CssClasses = new CssClasses(), + HostUrl = string.Empty, + PrismJsInterop = null, + }; + + using var markdonwConverter = new MarkdownConverter(settings); + + var rendered = markdonwConverter.RenderMarkdownToTerminal(md); + + if (arguments.OutputFile == "-") + WriteToStdout(rendered); + else + _fileSystem.WriteAllText(arguments.OutputFile, rendered); + + return ExitCodes.Success; } } From 982c9c0c203cd42415046026f57cdee9610c33c6 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sat, 27 Dec 2025 20:43:03 +0100 Subject: [PATCH 07/41] New help rendering --- Source/BookGen/Commands/HelpCommand.cs | 5 +- Source/BookGen/Infrastructure/HelpRenderer.cs | 113 +++--------------- 2 files changed, 20 insertions(+), 98 deletions(-) diff --git a/Source/BookGen/Commands/HelpCommand.cs b/Source/BookGen/Commands/HelpCommand.cs index 4f28c9fe..bc52e1c0 100644 --- a/Source/BookGen/Commands/HelpCommand.cs +++ b/Source/BookGen/Commands/HelpCommand.cs @@ -16,6 +16,7 @@ internal sealed class HelpCommand : Command { private readonly IHelpProvider _helpProvider; private readonly HashSet _commandNames; + private readonly HelpRenderer _renderer = new(); public HelpCommand(IHelpProvider helpProvider, ICommandRunnerProxy runnerProxy) { @@ -27,7 +28,7 @@ public override int Execute(IReadOnlyList context) { if (context.Count == 0) { - HelpRenderer.RenderHelp(_helpProvider.GetCommandHelp("help")); + _renderer.RenderHelp(_helpProvider.GetCommandHelp("help")); return ExitCodes.Success; } @@ -38,7 +39,7 @@ public override int Execute(IReadOnlyList context) return ExitCodes.GeneralError; } - HelpRenderer.RenderHelp(_helpProvider.GetCommandHelp(command)); + _renderer.RenderHelp(_helpProvider.GetCommandHelp(command)); return ExitCodes.Success; } diff --git a/Source/BookGen/Infrastructure/HelpRenderer.cs b/Source/BookGen/Infrastructure/HelpRenderer.cs index e64b9e0c..20b75284 100644 --- a/Source/BookGen/Infrastructure/HelpRenderer.cs +++ b/Source/BookGen/Infrastructure/HelpRenderer.cs @@ -3,113 +3,34 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- -using Spectre.Console; +using Bookgen.Lib.Markdown.Renderers.Terminal; -namespace BookGen.Infrastructure; - -public static class HelpRenderer -{ - public static string[][] GetPages(IEnumerable article) - { - int pageSize = Console.WindowHeight - 3; - IReadOnlyList reWraped = DoReWrap(article, pageSize, Console.WindowWidth); - return reWraped.Chunk(pageSize).ToArray(); - } +using Markdig; - public static void RenderPage(string[] pageContent) - { - foreach (var line in pageContent) - { - if (line.StartsWith("# ")) - AnsiConsole.MarkupInterpolated($"[green bold]{line}[/]{Environment.NewLine}"); - else if (line.StartsWith('`') || line.EndsWith('`')) - AnsiConsole.MarkupInterpolated($"[aqua]{line}[/]{Environment.NewLine}"); - else - AnsiConsole.MarkupInterpolated($"[italic]{line}[/]{Environment.NewLine}"); - } - } - - public static void RenderHelp(IEnumerable article) - { - var pages = GetPages(article); - Console.Clear(); +using Microsoft.AspNetCore.Components.RenderTree; - int currentPage = -1; - int nextPage = 0; - bool run = pages.Length > 1; - do - { - if (currentPage != nextPage) - { - currentPage = nextPage; - Console.Clear(); - RenderPage(pages[currentPage]); - RenderUsage(currentPage, pages.Length); - } - - if (!run) continue; +namespace BookGen.Infrastructure; - var key = Console.ReadKey(); - switch (key.Key) - { - case ConsoleKey.LeftArrow: - case ConsoleKey.UpArrow: - nextPage = CalculatePage(currentPage, pages.Length, -1); - break; - case ConsoleKey.DownArrow: - case ConsoleKey.RightArrow: - nextPage = CalculatePage(currentPage, pages.Length, +1); - break; - case ConsoleKey.Escape: - case ConsoleKey.Q: - run = false; - Console.Clear(); - break; - } - } - while (run); - } +internal sealed class HelpRenderer +{ + private readonly MarkdownPipeline _terminalPipeLine; - private static void RenderUsage(int currentPage, int pages) + public HelpRenderer() { - if (pages < 2) - return; - - AnsiConsole.WriteLine(); - AnsiConsole.MarkupInterpolated($"[teal]{currentPage + 1} of {pages}[/]"); - AnsiConsole.MarkupInterpolated($" [silver]ESC or Q: Exit, <- Prev, Next ->[/]{Environment.NewLine}"); + _terminalPipeLine = new MarkdownPipelineBuilder().Build(); } - private static int CalculatePage(int currentPage, int pages, int offset) + public void RenderHelp(IEnumerable article) { - int newIndex = currentPage + offset; - - if (newIndex < 0) - newIndex = 0; + string markdown = string.Join(Environment.NewLine, article); + using var writer = new StringWriter(); + var renderer = new VT100Renderer(writer, new PSMarkdownOptionInfo()); + string rendered = Markdown.Convert(markdown, renderer, _terminalPipeLine).ToString() ?? ""; - if (newIndex > pages - 1) - newIndex = pages - 1; + using var reader = new StringReader(rendered); - return newIndex; - } + Webmaster442.WindowsTerminal.Wigets.Pager pager = new(reader); - private static List DoReWrap(IEnumerable article, int pageSize, int windowWidth) - { - List result = new(pageSize); - foreach (string line in article) - { - if (line.Length > windowWidth) - { - var newLines = line - .Chunk(windowWidth) - .Select(chrs => new string(chrs)); - result.AddRange(newLines); - } - else - { - result.Add(line); - } - } - return result; + pager.Show(false); } } From 2c87ac298aba51a313fd9542221b78df4d7ba68c Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sun, 28 Dec 2025 15:06:16 +0100 Subject: [PATCH 08/41] Rewrote terminal rendering --- Source/BookGen/Infrastructure/HelpRenderer.cs | 15 +- Source/BookGen/Properties/launchSettings.json | 4 + Source/Bookgen.Lib/Bookgen.Lib.csproj | 2 +- .../Bookgen.Lib/Markdown/MarkdownConverter.cs | 19 +- .../Terminal/AutolinkInlineRenderer.cs | 25 ++ .../Renderers/Terminal/CodeBlockRenderer.cs | 40 ++++ .../Renderers/Terminal/CodeInlineRenderer.cs | 24 +- .../Terminal/DelimiterInlineRenderer.cs | 12 + .../Terminal/EmphasisInlineRenderer.cs | 46 +++- .../Terminal/FencedCodeBlockRenderer.cs | 39 ---- .../Renderers/Terminal/HeaderBlockRenderer.cs | 54 ----- .../Renderers/Terminal/HeadingRenderer.cs | 27 +++ .../Renderers/Terminal/LeafInlineRenderer.cs | 27 --- .../Terminal/LineBreakInlineRenderer.cs | 15 ++ .../Renderers/Terminal/LineBreakRenderer.cs | 26 --- .../Renderers/Terminal/LinkInlineRenderer.cs | 39 ++-- .../Renderers/Terminal/ListBlockRenderer.cs | 47 ---- .../Terminal/ListItemBlockRenderer.cs | 78 ------- .../Renderers/Terminal/ListRenderer.cs | 68 ++++++ .../Terminal/LiteralInlineRenderer.cs | 12 + .../Terminal/MarkdownOptionInfoProperty.cs | 65 ------ .../Terminal/PSMarkdownOptionInfo.cs | 200 ---------------- .../Terminal/ParagraphBlockRenderer.cs | 24 -- .../Renderers/Terminal/ParagraphRenderer.cs | 14 ++ .../Renderers/Terminal/QuoteBlockRenderer.cs | 32 ++- .../Renderers/Terminal/RenderOptions.cs | 41 ++++ .../Terminal/TerminalObjectRenderer.cs | 8 + .../Renderers/Terminal/TerminalRenderer.cs | 42 ++++ .../Terminal/ThematicBreakRenderer.cs | 12 + .../Terminal/VT100EscapeSequences.cs | 213 ------------------ .../Renderers/Terminal/VT100ObjectRenderer.cs | 15 -- .../Renderers/Terminal/VT100Renderer.cs | 40 ---- 32 files changed, 431 insertions(+), 894 deletions(-) create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/FencedCodeBlockRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeaderBlockRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeadingRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/LeafInlineRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListBlockRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListItemBlockRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/MarkdownOptionInfoProperty.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/PSMarkdownOptionInfo.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphBlockRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalObjectRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalRenderer.cs create mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/ThematicBreakRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100EscapeSequences.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100ObjectRenderer.cs delete mode 100644 Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100Renderer.cs diff --git a/Source/BookGen/Infrastructure/HelpRenderer.cs b/Source/BookGen/Infrastructure/HelpRenderer.cs index 20b75284..0ee95062 100644 --- a/Source/BookGen/Infrastructure/HelpRenderer.cs +++ b/Source/BookGen/Infrastructure/HelpRenderer.cs @@ -6,8 +6,7 @@ using Bookgen.Lib.Markdown.Renderers.Terminal; using Markdig; - -using Microsoft.AspNetCore.Components.RenderTree; +using Markdig.Parsers; namespace BookGen.Infrastructure; @@ -22,12 +21,16 @@ public HelpRenderer() public void RenderHelp(IEnumerable article) { - string markdown = string.Join(Environment.NewLine, article); + string md = string.Join(Environment.NewLine, article); + var document = MarkdownParser.Parse(md, _terminalPipeLine); + using var writer = new StringWriter(); - var renderer = new VT100Renderer(writer, new PSMarkdownOptionInfo()); - string rendered = Markdown.Convert(markdown, renderer, _terminalPipeLine).ToString() ?? ""; + var renderer = new TerminalRenderer(writer, new RenderOptions()); + + renderer.Render(document); + renderer.Writer.Flush(); - using var reader = new StringReader(rendered); + using var reader = new StringReader(writer.ToString()); Webmaster442.WindowsTerminal.Wigets.Pager pager = new(reader); diff --git a/Source/BookGen/Properties/launchSettings.json b/Source/BookGen/Properties/launchSettings.json index d3340b37..39bbda93 100644 --- a/Source/BookGen/Properties/launchSettings.json +++ b/Source/BookGen/Properties/launchSettings.json @@ -44,6 +44,10 @@ "Templates": { "commandName": "Project", "commandLineArgs": "templates" + }, + "Help": { + "commandName": "Project", + "commandLineArgs": "help" } } } \ No newline at end of file diff --git a/Source/Bookgen.Lib/Bookgen.Lib.csproj b/Source/Bookgen.Lib/Bookgen.Lib.csproj index e8c9dd53..5b8ae154 100644 --- a/Source/Bookgen.Lib/Bookgen.Lib.csproj +++ b/Source/Bookgen.Lib/Bookgen.Lib.csproj @@ -25,7 +25,7 @@ - + diff --git a/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs b/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs index c979d9a2..7e42a716 100644 --- a/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs +++ b/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs @@ -5,11 +5,9 @@ using Bookgen.Lib.Markdown.Renderers.Terminal; using Bookgen.Lib.Markdown.TableOfContents; -using Bookgen.Lib.Pipeline; using Markdig; - -using Microsoft.AspNetCore.Components; +using Markdig.Parsers; namespace Bookgen.Lib.Markdown; @@ -39,6 +37,7 @@ public MarkdownConverter(RenderSettings settings) _terminalPipeLine = new MarkdownPipelineBuilder() .UseYamlFrontMatter() + .UseAutoLinks() .Build(); } @@ -56,13 +55,19 @@ public void Dispose() public string RenderMarkdownToHtml(string markdown) => Markdig.Markdown.ToHtml(markdown, _htmlPipeLine); - public string RenderMarkdownToTerminal(string markdown) + public string RenderMarkdownToTerminal(string markdown, RenderOptions? renderOptions = null) { - PSMarkdownOptionInfo optionInfo = new(); + var document = MarkdownParser.Parse(markdown, _terminalPipeLine); using var writer = new StringWriter(); - var renderer = new VT100Renderer(writer, optionInfo); - return Markdig.Markdown.Convert(markdown, renderer, _terminalPipeLine).ToString() ?? ""; + renderOptions ??= new RenderOptions(); + + TerminalRenderer renderer = new TerminalRenderer(writer, renderOptions); + + renderer.Render(document); + renderer.Writer.Flush(); + + return renderer.Writer.ToString() ?? string.Empty; } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs new file mode 100644 index 00000000..9fe9d22c --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs @@ -0,0 +1,25 @@ +using Markdig.Syntax.Inlines; + +using System.Web; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class AutolinkInlineRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, AutolinkInline obj) + { + string url = obj.IsEmail + ? HttpUtility.UrlEncode($"mailto:{obj.Url}") + : HttpUtility.UrlEncode(obj.Url); + + var text = renderer + .Builder + .New() + .WithForegroundColor(renderer.RenderOptions.LinkColor) + .AppendLink(url, obj.Url) + .ResetFormat() + .ToString(); + + renderer.Write(text); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs new file mode 100644 index 00000000..a6c95b0c --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs @@ -0,0 +1,40 @@ +using Markdig.Helpers; +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class CodeBlockRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, CodeBlock obj) + { + if (obj?.Lines.Lines != null) + { + string begin = renderer.Builder + .New() + .WithBackgroundColor(renderer.RenderOptions.CodeBlockBackground) + .WithForegroundColor(renderer.RenderOptions.CodeBlockColor) + .ToString(); + + renderer.Write(begin); + + for (int i=0; i < obj.Lines.Count; i++) + { + StringLine codeLine = obj.Lines.Lines[i]; + if (!string.IsNullOrWhiteSpace(codeLine.ToString())) + { + if (i == obj.Lines.Count - 1) + { + renderer.Write(codeLine.ToString()).WriteReset().WriteLine(); + } + else + { + renderer.WriteLine(codeLine.ToString()); + } + + } + } + + renderer.WriteLine(); + } + } +} \ No newline at end of file diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeInlineRenderer.cs index 168158ce..aa9658bd 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeInlineRenderer.cs @@ -1,17 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax.Inlines; +using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; -/// -/// Renderer for adding VT100 escape sequences for inline code elements. -/// -internal class CodeInlineRenderer : VT100ObjectRenderer +internal sealed class CodeInlineRenderer : TerminalObjectRenderer { - protected override void Write(VT100Renderer renderer, CodeInline obj) + protected override void Write(TerminalRenderer renderer, CodeInline obj) { - renderer.Write(renderer.EscapeSequences.FormatCode(obj.Content, isInline: true)); + var begin = renderer.Builder + .New() + .WithForegroundColor(renderer.RenderOptions.CodeInlineColor) + .WithItalic() + .ToString(); + + renderer.Write(begin); + + renderer.Write(obj.ContentSpan); + + renderer.WriteReset(); } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs new file mode 100644 index 00000000..e9c1609d --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs @@ -0,0 +1,12 @@ +using Markdig.Syntax.Inlines; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class DelimiterInlineRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, DelimiterInline obj) + { + renderer.Write(obj.ToLiteral()); + renderer.WriteChildren(obj); + } +} \ No newline at end of file diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs index 8e585e71..7a52ceca 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs @@ -1,17 +1,45 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +using Markdig.Syntax.Inlines; -using Markdig.Syntax.Inlines; +using System.Diagnostics; namespace Bookgen.Lib.Markdown.Renderers.Terminal; -/// -/// Renderer for adding VT100 escape sequences for bold and italics elements. -/// -internal class EmphasisInlineRenderer : VT100ObjectRenderer +internal sealed class EmphasisInlineRenderer : TerminalObjectRenderer { - protected override void Write(VT100Renderer renderer, EmphasisInline obj) + private enum RenderAs { - renderer.Write(renderer.EscapeSequences.FormatEmphasis(obj.FirstChild?.ToString() ?? "", isBold: obj.DelimiterCount == 2)); + Regular = 0, + Bold, + Italic, + } + + private static RenderAs GetRenderOption(EmphasisInline obj) + { + if (obj.DelimiterChar is '*' or '_') + { + Debug.Assert(obj.DelimiterCount <= 2); + return obj.DelimiterCount == 2 ? RenderAs.Bold : RenderAs.Italic; + } + return RenderAs.Regular; + } + + protected override void Write(TerminalRenderer renderer, EmphasisInline obj) + { + RenderAs option = GetRenderOption(obj); + if (option == RenderAs.Regular) + { + renderer.WriteChildren(obj); + return; + } + + var preformat = renderer.Builder.New(); + + if (option == RenderAs.Bold) + preformat.WithBold(); + else + preformat.WithItalic(); + + renderer.Write(preformat.ToString()).WriteChildren(obj); + renderer.WriteReset(); } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/FencedCodeBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/FencedCodeBlockRenderer.cs deleted file mode 100644 index b41df71a..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/FencedCodeBlockRenderer.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Helpers; -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for code blocks with language type. -/// -internal class FencedCodeBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, FencedCodeBlock obj) - { - if (obj?.Lines.Lines != null) - { - foreach (StringLine codeLine in obj.Lines.Lines) - { - if (!string.IsNullOrWhiteSpace(codeLine.ToString())) - { - // If the code block is of type YAML, then tab to right to improve readability. - // This specifically helps for parameters help content. - if (string.Equals(obj.Info, "yaml", StringComparison.OrdinalIgnoreCase)) - { - renderer.Write("\t").WriteLine(codeLine.ToString()); - } - else - { - renderer.WriteLine(renderer.EscapeSequences.FormatCode(codeLine.ToString(), isInline: false)); - } - } - } - - // Add a blank line after the code block for better readability. - renderer.WriteLine(); - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeaderBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeaderBlockRenderer.cs deleted file mode 100644 index 342f2837..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeaderBlockRenderer.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for headings. -/// -internal class HeaderBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, HeadingBlock obj) - { - string? headerText = obj.Inline?.FirstChild?.ToString(); - - if (!string.IsNullOrEmpty(headerText)) - { - // Format header and then add blank line to improve readability. - switch (obj.Level) - { - case 1: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader1(headerText)); - renderer.WriteLine(); - break; - - case 2: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader2(headerText)); - renderer.WriteLine(); - break; - - case 3: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader3(headerText)); - renderer.WriteLine(); - break; - - case 4: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader4(headerText)); - renderer.WriteLine(); - break; - - case 5: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader5(headerText)); - renderer.WriteLine(); - break; - - case 6: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader6(headerText)); - renderer.WriteLine(); - break; - } - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeadingRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeadingRenderer.cs new file mode 100644 index 00000000..dd275d5b --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeadingRenderer.cs @@ -0,0 +1,27 @@ +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class HeadingRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, HeadingBlock obj) + { + string prefix = new string('#', obj.Level); + + var beginText = renderer + .Builder + .New() + .WithForegroundColor(renderer.RenderOptions.HeadingColor) + .WithBold() + .Append(prefix) + .Append(' ') + .ToString(); + + renderer + .Write(beginText) + .WriteLeafInline(obj) + .WriteReset() + .EnsureLine() + .WriteLine(); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LeafInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LeafInlineRenderer.cs deleted file mode 100644 index 44a3333d..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LeafInlineRenderer.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax.Inlines; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for leaf elements like plain text in paragraphs. -/// -internal class LeafInlineRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, LeafInline obj) - { - // If the next sibling is null, then this is the last line in the paragraph. - // Add new line character at the end. - // Else just write without newline at the end. - if (obj.NextSibling == null) - { - renderer.WriteLine(obj.ToString() ?? ""); - } - else - { - renderer.Write(obj.ToString()); - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs new file mode 100644 index 00000000..ee1f1fe7 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs @@ -0,0 +1,15 @@ +using Markdig.Syntax.Inlines; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class LineBreakInlineRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, LineBreakInline obj) + { + if (obj.IsHard) + { + renderer.WriteLine(); + } + renderer.EnsureLine(); + } +} \ No newline at end of file diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakRenderer.cs deleted file mode 100644 index 2d7054b2..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakRenderer.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax.Inlines; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for line breaks. -/// -internal class LineBreakRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, LineBreakInline obj) - { - // If it is a hard line break add new line at the end. - // Else, add a space for after the last character to improve readability. - if (obj.IsHard) - { - renderer.WriteLine(); - } - else - { - renderer.Write(" "); - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs index a27491eb..02e6e93b 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs @@ -1,27 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax.Inlines; +using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; -/// -/// Renderer for adding VT100 escape sequences for links. -/// -internal class LinkInlineRenderer : VT100ObjectRenderer +internal sealed class LinkInlineRenderer : TerminalObjectRenderer { - protected override void Write(VT100Renderer renderer, LinkInline obj) + protected override void Write(TerminalRenderer renderer, LinkInline obj) { - string? text = obj.FirstChild?.ToString(); - - // Format link as image or link. if (obj.IsImage) { - renderer.Write(renderer.EscapeSequences.FormatImage(text)); + // TODO + return; } - else + + string? linkText = obj.FirstChild?.ToString(); + + if (obj.Url is null + || linkText is null) { - renderer.Write(renderer.EscapeSequences.FormatLink(text ?? "", obj.Url ?? "")); + return; } + + string text = renderer + .Builder + .New() + .WithForegroundColor(renderer.RenderOptions.LinkColor) + .AppendLink(obj.Url, linkText) + .ResetFormat() + .ToString(); + + renderer.Write(text); + } -} +} \ No newline at end of file diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListBlockRenderer.cs deleted file mode 100644 index b3a1f850..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListBlockRenderer.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for list blocks. -/// -internal class ListBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, ListBlock obj) - { - // start index of a numbered block. - int index = 1; - - foreach (var item in obj) - { - if (item is ListItemBlock listItem) - { - if (obj.IsOrdered) - { - RenderNumberedList(renderer, listItem, index++); - } - else - { - renderer.Write(listItem); - } - } - } - - renderer.WriteLine(); - } - - private static void RenderNumberedList(VT100Renderer renderer, ListItemBlock block, int index) - { - // For a numbered list, we need to make sure the index is incremented. - foreach (var line in block) - { - if (line is ParagraphBlock paragraphBlock && paragraphBlock.Inline != null) - { - renderer.Write(index.ToString()).Write(". ").Write(paragraphBlock.Inline); - } - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListItemBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListItemBlockRenderer.cs deleted file mode 100644 index 7d4b60b0..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListItemBlockRenderer.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for items in a list block. -/// -internal class ListItemBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, ListItemBlock obj) - { - if (obj.Parent is ListBlock parent) - { - if (!parent.IsOrdered) - { - foreach (var line in obj) - { - RenderWithIndent(renderer, line, parent.BulletType, 0); - } - } - } - } - - private static void RenderWithIndent(VT100Renderer renderer, MarkdownObject block, char listBullet, int indentLevel) - { - // Indent left by 2 for each level on list. - string indent = Padding(indentLevel * 2); - - if (block is ParagraphBlock paragraphBlock && paragraphBlock.Inline != null) - { - renderer.Write(indent).Write(listBullet).Write(" ").Write(paragraphBlock.Inline); - } - else - { - // If there is a sublist, the block is a ListBlock instead of ParagraphBlock. - if (block is ListBlock subList) - { - foreach (var subListItem in subList) - { - if (subListItem is ListItemBlock subListItemBlock) - { - foreach (var line in subListItemBlock) - { - // Increment indent level for sub list. - RenderWithIndent(renderer, line, listBullet, indentLevel + 1); - } - } - } - } - } - } - - // Typical padding is at most a screen's width, any more than that and we won't bother caching. - private const int IndentCacheMax = 120; - - private static readonly string[] IndentCache = new string[IndentCacheMax]; - - internal static string Padding(int countOfSpaces) - { - if (countOfSpaces >= IndentCacheMax) - { - return new string(' ', countOfSpaces); - } - - var result = IndentCache[countOfSpaces]; - - if (result == null) - { - Interlocked.CompareExchange(ref IndentCache[countOfSpaces], new string(' ', countOfSpaces), comparand: null); - result = IndentCache[countOfSpaces]; - } - - return result; - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs new file mode 100644 index 00000000..2c735c58 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs @@ -0,0 +1,68 @@ +using Markdig.Syntax; + +using System.Security.Cryptography.X509Certificates; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class ListRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, ListBlock obj) + { + int indent = 0; + string sub = ""; + Write(renderer, obj, sub, ref indent); + } + + private static void Write(TerminalRenderer renderer, ListBlock obj, string sub, ref int indent) + { + const int indentSize = 2; + if (!obj.IsOrdered) + { + foreach (ListItemBlock item in obj.Cast()) + { + renderer.Write(new string(' ', indent * indentSize)); + renderer.Write("* "); + for (int i = 0; i < item.Count; i++) + { + Block subBlock = item[i]; + if (subBlock is ListBlock subListBlock) + { + indent++; + Write(renderer, subListBlock, "", ref indent); + } + else + { + renderer.Render(subBlock); + } + } + } + } + else + { + int number = 1; + foreach (ListItemBlock item in obj.Cast()) + { + renderer.Write(new string(' ', indent * indentSize)) + .Write(sub) + .Write(number.ToString()) + .Write(". "); + + for (int i = 0; i < item.Count; i++) + { + Block subBlock = item[i]; + if (subBlock is ListBlock subListBlock) + { + indent++; + var nsub = $"{sub} {number}."; + Write(renderer, subListBlock, nsub, ref indent); + } + else + { + renderer.Render(subBlock); + } + } + ++number; + } + } + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs new file mode 100644 index 00000000..e03e404d --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs @@ -0,0 +1,12 @@ +using Markdig.Syntax.Inlines; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class LiteralInlineRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, LiteralInline obj) + { + string content = obj.Content.ToString(); + renderer.Write(content); + } +} \ No newline at end of file diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/MarkdownOptionInfoProperty.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/MarkdownOptionInfoProperty.cs deleted file mode 100644 index 64406509..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/MarkdownOptionInfoProperty.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Enum to name all the properties of PSMarkdownOptionInfo. -/// -public enum MarkdownOptionInfoProperty -{ - /// - /// Property name Header1. - /// - Header1, - - /// - /// Property name Header2. - /// - Header2, - - /// - /// Property name Header3. - /// - Header3, - - /// - /// Property name Header4. - /// - Header4, - - /// - /// Property name Header5. - /// - Header5, - - /// - /// Property name Header6. - /// - Header6, - - /// - /// Property name Code. - /// - Code, - - /// - /// Property name Link. - /// - Link, - - /// - /// Property name Image. - /// - Image, - - /// - /// Property name EmphasisBold. - /// - EmphasisBold, - - /// - /// Property name EmphasisItalics. - /// - EmphasisItalics -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/PSMarkdownOptionInfo.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/PSMarkdownOptionInfo.cs deleted file mode 100644 index dbb230cc..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/PSMarkdownOptionInfo.cs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Class to represent color preference options for various Markdown elements. -/// -public sealed class PSMarkdownOptionInfo -{ - private const char Esc = (char)0x1b; - private const string EndSequence = "[0m"; - - /// - /// Gets or sets current VT100 escape sequence for header 1. - /// - public string Header1 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 2. - /// - public string Header2 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 3. - /// - public string Header3 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 4. - /// - public string Header4 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 5. - /// - public string Header5 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 6. - /// - public string Header6 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for code inline and code blocks. - /// - public string Code { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for links. - /// - public string Link { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for images. - /// - public string Image { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for bold text. - /// - public string EmphasisBold { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for italics text. - /// - public string EmphasisItalics { get; set; } - - /// - /// Gets or sets a value indicating whether VT100 escape sequences should be added. Default it true. - /// - public bool EnableVT100Encoding { get; set; } - - /// - /// Get the property as an rendered escape sequence. - /// This is used by formatting system for displaying. - /// - /// Name of the property to get as escape sequence. - /// Specified property name as escape sequence. - public string AsEscapeSequence(MarkdownOptionInfoProperty propertyName) - { - return propertyName switch - { - MarkdownOptionInfoProperty.Header1 => string.Concat(Esc, Header1, Header1, Esc, EndSequence), - MarkdownOptionInfoProperty.Header2 => string.Concat(Esc, Header2, Header2, Esc, EndSequence), - MarkdownOptionInfoProperty.Header3 => string.Concat(Esc, Header3, Header3, Esc, EndSequence), - MarkdownOptionInfoProperty.Header4 => string.Concat(Esc, Header4, Header4, Esc, EndSequence), - MarkdownOptionInfoProperty.Header5 => string.Concat(Esc, Header5, Header5, Esc, EndSequence), - MarkdownOptionInfoProperty.Header6 => string.Concat(Esc, Header6, Header6, Esc, EndSequence), - MarkdownOptionInfoProperty.Code => string.Concat(Esc, Code, Code, Esc, EndSequence), - MarkdownOptionInfoProperty.Link => string.Concat(Esc, Link, Link, Esc, EndSequence), - MarkdownOptionInfoProperty.Image => string.Concat(Esc, Image, Image, Esc, EndSequence), - MarkdownOptionInfoProperty.EmphasisBold => string.Concat(Esc, EmphasisBold, EmphasisBold, Esc, EndSequence), - MarkdownOptionInfoProperty.EmphasisItalics => string.Concat(Esc, EmphasisItalics, EmphasisItalics, Esc, EndSequence), - _ => throw new InvalidOperationException($"Unknown value: {propertyName}"), - }; - } - - /// - /// Initializes a new instance of the class and sets dark as the default theme. - /// - public PSMarkdownOptionInfo() - { - SetDarkTheme(); - EnableVT100Encoding = true; - } - - private const string Header1Dark = "[7m"; - private const string Header2Dark = "[4;93m"; - private const string Header3Dark = "[4;94m"; - private const string Header4Dark = "[4;95m"; - private const string Header5Dark = "[4;96m"; - private const string Header6Dark = "[4;97m"; - private const string CodeDark = "[48;2;155;155;155;38;2;30;30;30m"; - private const string CodeMacOS = "[107;95m"; - private const string LinkDark = "[4;38;5;117m"; - private const string ImageDark = "[33m"; - private const string EmphasisBoldDark = "[1m"; - private const string EmphasisItalicsDark = "[36m"; - - private const string Header1Light = "[7m"; - private const string Header2Light = "[4;33m"; - private const string Header3Light = "[4;34m"; - private const string Header4Light = "[4;35m"; - private const string Header5Light = "[4;36m"; - private const string Header6Light = "[4;30m"; - private const string CodeLight = "[48;2;155;155;155;38;2;30;30;30m"; - private const string LinkLight = "[4;38;5;117m"; - private const string ImageLight = "[33m"; - private const string EmphasisBoldLight = "[1m"; - private const string EmphasisItalicsLight = "[36m"; - - /// - /// Set all preference for dark theme. - /// - [MemberNotNull(nameof(Header1), - nameof(Header2), - nameof(Header3), - nameof(Header4), - nameof(Header5), - nameof(Header6), - nameof(Link), - nameof(Image), - nameof(EmphasisBold), - nameof(EmphasisItalics), - nameof(Code))] - public void SetDarkTheme() - { - Header1 = Header1Dark; - Header2 = Header2Dark; - Header3 = Header3Dark; - Header4 = Header4Dark; - Header5 = Header5Dark; - Header6 = Header6Dark; - Link = LinkDark; - Image = ImageDark; - EmphasisBold = EmphasisBoldDark; - EmphasisItalics = EmphasisItalicsDark; - SetCodeColor(isDarkTheme: true); - } - - /// - /// Set all preference for light theme. - /// - [MemberNotNull(nameof(Header1), - nameof(Header2), - nameof(Header3), - nameof(Header4), - nameof(Header5), - nameof(Header6), - nameof(Link), - nameof(Image), - nameof(EmphasisBold), - nameof(EmphasisItalics), - nameof(Code))] - public void SetLightTheme() - { - Header1 = Header1Light; - Header2 = Header2Light; - Header3 = Header3Light; - Header4 = Header4Light; - Header5 = Header5Light; - Header6 = Header6Light; - Link = LinkLight; - Image = ImageLight; - EmphasisBold = EmphasisBoldLight; - EmphasisItalics = EmphasisItalicsLight; - SetCodeColor(isDarkTheme: false); - } - - [MemberNotNull(nameof(Code))] - private void SetCodeColor(bool isDarkTheme) - { - // MacOS terminal app does not support extended colors for VT100, so we special case for it. - Code = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? CodeMacOS : isDarkTheme ? CodeDark : CodeLight; - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphBlockRenderer.cs deleted file mode 100644 index e063ccb0..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphBlockRenderer.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for paragraphs. -/// -internal class ParagraphBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, ParagraphBlock obj) - { - if (obj.Inline == null) - return; - - // Call the renderer for children, leaf inline or line breaks. - renderer.WriteChildren(obj.Inline); - - // Add new line at the end of the paragraph. - renderer.WriteLine(); - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphRenderer.cs new file mode 100644 index 00000000..b566102c --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphRenderer.cs @@ -0,0 +1,14 @@ +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class ParagraphRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, ParagraphBlock obj) + { + renderer + .WriteLeafInline(obj) + .WriteLine() + .WriteLine(); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs index ae2639d0..4a4df4c9 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs @@ -1,24 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; +using Markdig.Syntax; namespace Bookgen.Lib.Markdown.Renderers.Terminal; -/// -/// Renderer for adding VT100 escape sequences for quote blocks. -/// -internal class QuoteBlockRenderer : VT100ObjectRenderer +internal sealed class QuoteBlockRenderer : TerminalObjectRenderer { - protected override void Write(VT100Renderer renderer, QuoteBlock obj) + protected override void Write(TerminalRenderer renderer, QuoteBlock obj) { - // Iterate through each item and add the quote character before the content. - foreach (var item in obj) - { - renderer.Write(obj.QuoteChar).Write(" ").Write(item); - } + var begin = renderer.Builder.New() + .WithForegroundColor(renderer.RenderOptions.QuoteBlockColor) + .WithItalic() + .ToString(); + + renderer + .Write(begin) + .WriteChildren(obj); + + renderer + .WriteReset(); - // Add blank line after the quote block. - renderer.WriteLine(); } -} +} \ No newline at end of file diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs new file mode 100644 index 00000000..c7f0f2b9 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs @@ -0,0 +1,41 @@ +using Webmaster442.WindowsTerminal; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +public sealed class RenderOptions +{ + public TerminalColor HeadingColor { get; set; } + + public TerminalColor LinkColor { get; set; } + + public TerminalColor CodeInlineColor { get; set; } + + public TerminalColor QuoteBlockColor { get; set; } + + public TerminalColor CodeBlockBackground { get; set; } + + public TerminalColor CodeBlockColor { get; set; } + + public int Width + { + get => field; + set + { + if (value < 1) + throw new ArgumentOutOfRangeException(nameof(Width), "Width must be greater than 0."); + + field = value; + } + } + + public RenderOptions() + { + HeadingColor = TerminalColor.Green; + LinkColor = TerminalColor.BrightBlue; + CodeInlineColor = TerminalColor.BrightRed; + QuoteBlockColor = TerminalColor.BrightWhite; + CodeBlockColor = TerminalColor.Black; + CodeBlockBackground = TerminalColor.White; + Width = 120; + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalObjectRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalObjectRenderer.cs new file mode 100644 index 00000000..a97ebc24 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalObjectRenderer.cs @@ -0,0 +1,8 @@ +using Markdig.Renderers; +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +public abstract class TerminalObjectRenderer : MarkdownObjectRenderer where TObject : MarkdownObject +{ +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalRenderer.cs new file mode 100644 index 00000000..a587a8ca --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalRenderer.cs @@ -0,0 +1,42 @@ +using Markdig.Renderers; + +using Webmaster442.WindowsTerminal; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +public sealed class TerminalRenderer : TextRendererBase +{ + + public TerminalRenderer(TextWriter writer, RenderOptions renderOptions) : base(writer) + { + RenderOptions = renderOptions; + Builder = new TerminalFormattedStringBuilder(); + + ObjectRenderers.Add(new CodeBlockRenderer()); + ObjectRenderers.Add(new ListRenderer()); + ObjectRenderers.Add(new HeadingRenderer()); + ObjectRenderers.Add(new ParagraphRenderer()); + ObjectRenderers.Add(new QuoteBlockRenderer()); + ObjectRenderers.Add(new ThematicBreakRenderer()); + + ObjectRenderers.Add(new AutolinkInlineRenderer()); + ObjectRenderers.Add(new CodeInlineRenderer()); + ObjectRenderers.Add(new DelimiterInlineRenderer()); + ObjectRenderers.Add(new EmphasisInlineRenderer()); + ObjectRenderers.Add(new LineBreakInlineRenderer()); + ObjectRenderers.Add(new LinkInlineRenderer()); + ObjectRenderers.Add(new LiteralInlineRenderer()); + } + + public RenderOptions RenderOptions { get; } + + internal TerminalFormattedStringBuilder Builder { get; } + + private const string ResetCode = "\u001b[0m"; + + public TerminalRenderer WriteReset() + { + Write(ResetCode); + return this; + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ThematicBreakRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ThematicBreakRenderer.cs new file mode 100644 index 00000000..18b01324 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ThematicBreakRenderer.cs @@ -0,0 +1,12 @@ +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class ThematicBreakRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, ThematicBreakBlock obj) + { + renderer.WriteLine(new string('-', renderer.RenderOptions.Width)); + renderer.WriteLine(); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100EscapeSequences.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100EscapeSequences.cs deleted file mode 100644 index 6c174bd1..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100EscapeSequences.cs +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Class to represent default VT100 escape sequences. -/// -public class VT100EscapeSequences -{ - private const char Esc = (char)0x1B; - - private readonly string _endSequence = Esc + "[0m"; - - // For code blocks, [500@ make sure that the whole line has background color. - private const string LongBackgroundCodeBlock = "[500@"; - - private readonly PSMarkdownOptionInfo _options; - - /// - /// Initializes a new instance of the class. - /// - /// PSMarkdownOptionInfo object to initialize with. - public VT100EscapeSequences(PSMarkdownOptionInfo optionInfo) - { - _options = optionInfo ?? throw new ArgumentNullException(nameof(optionInfo)); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 1 string. - public string FormatHeader1(string headerText) - { - return FormatHeader(headerText, _options.Header1); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 2 string. - public string FormatHeader2(string headerText) - { - return FormatHeader(headerText, _options.Header2); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 3 string. - public string FormatHeader3(string headerText) - { - return FormatHeader(headerText, _options.Header3); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 4 string. - public string FormatHeader4(string headerText) - { - return FormatHeader(headerText, _options.Header4); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 5 string. - public string FormatHeader5(string headerText) - { - return FormatHeader(headerText, _options.Header5); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 6 string. - public string FormatHeader6(string headerText) - { - return FormatHeader(headerText, _options.Header6); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the code block to format. - /// True if it is a inline code block, false otherwise. - /// Formatted code block string. - public string FormatCode(string codeText, bool isInline) - { - bool isVT100Enabled = _options.EnableVT100Encoding; - - if (isInline) - { - if (isVT100Enabled) - { - return string.Concat(Esc, _options.Code, codeText, _endSequence); - } - else - { - return codeText; - } - } - else - { - if (isVT100Enabled) - { - return string.Concat(Esc, _options.Code, codeText, Esc, LongBackgroundCodeBlock, _endSequence); - } - else - { - return codeText; - } - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the link to format. - /// URL of the link. - /// True url should be hidden, false otherwise. Default is true. - /// Formatted link string. - public string FormatLink(string linkText, string url, bool hideUrl = true) - { - bool isVT100Enabled = _options.EnableVT100Encoding; - - if (hideUrl) - { - if (isVT100Enabled) - { - return string.Concat(Esc, _options.Link, "\"", linkText, "\"", _endSequence); - } - else - { - return string.Concat("\"", linkText, "\""); - } - } - else - { - if (isVT100Enabled) - { - return string.Concat("\"", linkText, "\" (", Esc, _options.Link, url, _endSequence, ")"); - } - else - { - return string.Concat("\"", linkText, "\" (", url, ")"); - } - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text to format as emphasis. - /// True if it is to be formatted as bold, false to format it as italics. - /// Formatted emphasis string. - public string FormatEmphasis(string emphasisText, bool isBold) - { - var sequence = isBold ? _options.EmphasisBold : _options.EmphasisItalics; - - if (_options.EnableVT100Encoding) - { - return string.Concat(Esc, sequence, emphasisText, _endSequence); - } - else - { - return emphasisText; - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the image to format. - /// Formatted image string. - public string FormatImage(string? altText) - { - var text = altText; - - if (string.IsNullOrEmpty(altText)) - { - text = "Image"; - } - - if (_options.EnableVT100Encoding) - { - return string.Concat(Esc, _options.Image, "[", text, "]", _endSequence); - } - else - { - return string.Concat("[", text, "]"); - } - } - - private string FormatHeader(string headerText, string headerEscapeSequence) - { - if (_options.EnableVT100Encoding) - { - return string.Concat(Esc, headerEscapeSequence, headerText, _endSequence); - } - else - { - return headerText; - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100ObjectRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100ObjectRenderer.cs deleted file mode 100644 index 04f144c1..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100ObjectRenderer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Implement the MarkdownObjectRenderer with VT100Renderer. -/// -/// The element type of the renderer. -public abstract class VT100ObjectRenderer : MarkdownObjectRenderer where T : MarkdownObject -{ -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100Renderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100Renderer.cs deleted file mode 100644 index ff25494a..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100Renderer.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Renderers; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Initializes an instance of the VT100 renderer. -/// -public sealed class VT100Renderer : TextRendererBase -{ - /// - /// Initializes a new instance of the class. - /// - /// TextWriter to write to. - /// PSMarkdownOptionInfo object with options. - public VT100Renderer(TextWriter writer, PSMarkdownOptionInfo optionInfo) : base(writer) - { - EscapeSequences = new VT100EscapeSequences(optionInfo); - - // Add the various element renderers. - ObjectRenderers.Add(new HeaderBlockRenderer()); - ObjectRenderers.Add(new LineBreakRenderer()); - ObjectRenderers.Add(new CodeInlineRenderer()); - ObjectRenderers.Add(new FencedCodeBlockRenderer()); - ObjectRenderers.Add(new EmphasisInlineRenderer()); - ObjectRenderers.Add(new ParagraphBlockRenderer()); - ObjectRenderers.Add(new LeafInlineRenderer()); - ObjectRenderers.Add(new LinkInlineRenderer()); - ObjectRenderers.Add(new ListBlockRenderer()); - ObjectRenderers.Add(new ListItemBlockRenderer()); - ObjectRenderers.Add(new QuoteBlockRenderer()); - } - - /// - /// Gets the current escape sequences. - /// - public VT100EscapeSequences EscapeSequences { get; } -} From a0e9672fd5d398f038fa17300c95daccecde06c9 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 6 Feb 2026 16:28:38 +0100 Subject: [PATCH 09/41] Publish refactor --- Installers/debian-info.txt | 2 - Installers/install.sh | 13 ----- Installers/prepare-debian-base.sh | 13 ----- publish.ps1 | 90 +++++++++++++++++++++---------- 4 files changed, 61 insertions(+), 57 deletions(-) delete mode 100644 Installers/debian-info.txt delete mode 100644 Installers/install.sh delete mode 100644 Installers/prepare-debian-base.sh diff --git a/Installers/debian-info.txt b/Installers/debian-info.txt deleted file mode 100644 index 95c8a9ab..00000000 --- a/Installers/debian-info.txt +++ /dev/null @@ -1,2 +0,0 @@ -Debian user: user -Debian password: pass \ No newline at end of file diff --git a/Installers/install.sh b/Installers/install.sh deleted file mode 100644 index 3100d2ee..00000000 --- a/Installers/install.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -#check for admin rights -if [ "$EUID" -ne 0 ] - then echo "Please run as root" - exit -fi - -mkdir -p /opt/bookgen -cp bin/* /opt/bookgen -ln -s /opt/bookgen/BookGen /usr/bin/bookgen - -echo "BookGen installed successfully!" \ No newline at end of file diff --git a/Installers/prepare-debian-base.sh b/Installers/prepare-debian-base.sh deleted file mode 100644 index a59c19e3..00000000 --- a/Installers/prepare-debian-base.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -#check for admin rights -if [ "$EUID" -ne 0 ] - then echo "Please run as root" - exit -fi - -apt update -apt dist-upgrade -y -apt install -y curl wget mc -apt autoremove --purge -apt clean \ No newline at end of file diff --git a/publish.ps1 b/publish.ps1 index cdfe4433..f67a2c75 100644 --- a/publish.ps1 +++ b/publish.ps1 @@ -1,31 +1,63 @@ Clear-Host -# publish windows & linux self-contained -dotnet publish -c release -o "bin\publish\windows\bin" --self-contained true -r win-x64 -p PublishReadyToRun BookGen.slnx -dotnet publish -c release -o "bin\publish\linux\bin" --self-contained true -r linux-x64 -p PublishReadyToRun BookGen.slnx - -# copy installer scripts -Copy-Item "Installers\install.cmd" "bin\publish\windows\install.cmd" -Copy-Item "Installers\install.sh" "bin\publish\linux\install.sh" - -# copy assets -Copy-Item "bin\Release\assets.zip" "bin\publish\windows\bin\assets.zip" -Copy-Item "bin\Release\assets.zip" "bin\publish\linux\bin\assets.zip" - -# write version.txt -.\bin\publish\windows\bin\BookGen.exe version > .\bin\publish\windows\version.txt -.\bin\publish\windows\bin\BookGen version > .\bin\publish\linux\version.txt - -# Generate docs -.\bin\publish\windows\bin\BookGen Schemas -.\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\windows\Schemas.html" -t "Configuration schemas" -.\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\linux\Schemas.html" -t "Configuration schemas" -.\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\windows\Changelog.html" -t "Change Log" -.\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\linux\Changelog.html" -t "Change Log" -.\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\windows\Commands.html" -t "BookGen Commands" -.\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\linux\Commands.html" -t "BookGen Commands" -Remove-Item Schemas.md - -# zip -Compress-Archive -Path "bin\publish\windows\*" -DestinationPath "bin\publish\BookGen-windows.zip" -Force -tar -czvf "bin\publish\BookGen-linux.tar.gz" -C "bin\publish\linux" . +function Invoke-Publish { + param( + [bool] $SelfContained, + [string] $WindowsArchiveName, + [string] $LinuxArchiveName + ) + + if (Test-Path "bin\publish\windows") { + Remove-Item "bin\publish\windows*" -Recurse -Force + } + + if (Test-Path "bin\publish\linux") { + Remove-Item "bin\publish\windows*" -Recurse -Force + } + + # publish windows & linux + if ($SelfContained) { + dotnet publish -c release -o "bin\publish\windows\bin" --self-contained true -r win-x64 -p PublishReadyToRun BookGen.slnx + dotnet publish -c release -o "bin\publish\linux\bin" --self-contained true -r linux-x64 -p PublishReadyToRun BookGen.slnx + } + else { + dotnet publish -c release -o "bin\publish\windows\bin" -r win-x64 -p PublishReadyToRun BookGen.slnx + dotnet publish -c release -o "bin\publish\linux\bin" -r linux-x64 -p PublishReadyToRun BookGen.slnx + } + + # copy installer scripts + Copy-Item "Installers\install.cmd" "bin\publish\windows\install.cmd" + + # copy assets + Copy-Item "bin\Release\assets.zip" "bin\publish\windows\bin\assets.zip" + Copy-Item "bin\Release\assets.zip" "bin\publish\linux\bin\assets.zip" + + # write version.txt + .\bin\publish\windows\bin\BookGen.exe version > .\bin\publish\windows\version.txt + .\bin\publish\windows\bin\BookGen version > .\bin\publish\linux\version.txt + + # Generate docs + .\bin\publish\windows\bin\BookGen Schemas + .\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\windows\Schemas.html" -t "Configuration schemas" + .\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\linux\Schemas.html" -t "Configuration schemas" + .\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\windows\Changelog.html" -t "Change Log" + .\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\linux\Changelog.html" -t "Change Log" + .\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\windows\Commands.html" -t "BookGen Commands" + .\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\linux\Commands.html" -t "BookGen Commands" + Remove-Item Schemas.md + + # zip + if ($SelfContained) { + Compress-Archive -Path "bin\publish\windows\*" -DestinationPath "bin\publish\$WindowsArchiveName" -Force + tar -czvf "bin\publish\$LinuxArchiveName" -C "bin\publish\linux" . + } + else { + Compress-Archive -Path "bin\publish\windows\*" -DestinationPath "bin\publish\$WindowsArchiveName" -Force + tar -czvf "bin\publish\$LinuxArchiveName" -C "bin\publish\linux" . + } +} + +# Self-contained build and archives +Invoke-Publish -SelfContained $true -WindowsArchiveName "BookGen-windows-selefcontained.zip" -LinuxArchiveName "BookGen-linux-selefcontained.tar.gz" +# Framework-dependent build and archives +Invoke-Publish -SelfContained $false -WindowsArchiveName "BookGen-windows.zip" -LinuxArchiveName "BookGen-linux.tar.gz" \ No newline at end of file From c543f20c99e7940e3cd6c4e6e7881e382758447b Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 6 Feb 2026 16:29:41 +0100 Subject: [PATCH 10/41] Dependencies update --- BookGen.slnx | 2 - Directory.Packages.props | 14 +- Source/BookGen.Cli/packages.lock.json | 14 +- .../BookGen.Shell.Shared/packages.lock.json | 14 +- Source/BookGen.Shellprog/packages.lock.json | 122 +++++++------- Source/BookGen/packages.lock.json | 158 +++++++++--------- Source/Bookgen.Lib/packages.lock.json | 32 ++-- 7 files changed, 177 insertions(+), 179 deletions(-) diff --git a/BookGen.slnx b/BookGen.slnx index ec74988d..e45768c8 100644 --- a/BookGen.slnx +++ b/BookGen.slnx @@ -13,8 +13,6 @@ - - diff --git a/Directory.Packages.props b/Directory.Packages.props index a15ea897..30d8fc8e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,23 +9,23 @@ - - - - + + + + - + - - + + diff --git a/Source/BookGen.Cli/packages.lock.json b/Source/BookGen.Cli/packages.lock.json index e1bba11c..d09f29db 100644 --- a/Source/BookGen.Cli/packages.lock.json +++ b/Source/BookGen.Cli/packages.lock.json @@ -4,17 +4,17 @@ "net10.0": { "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" } }, "bookgen.vfs": { diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json index 012f2179..999b8ee4 100644 --- a/Source/BookGen.Shell.Shared/packages.lock.json +++ b/Source/BookGen.Shell.Shared/packages.lock.json @@ -4,11 +4,11 @@ "net10.0": { "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" } }, "Webmaster442.WindowsTerminal": { @@ -19,9 +19,9 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" } } } diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json index 5cdb23de..f9ce9d67 100644 --- a/Source/BookGen.Shellprog/packages.lock.json +++ b/Source/BookGen.Shellprog/packages.lock.json @@ -4,26 +4,26 @@ "net10.0": { "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "a0EWuBs6D3d7XMGroDXm+WsAi5CVVfjOJvyxurzWnuhBN9CO+1qHKcrKV1JK7H/T4ZtHIoVCOX/YyWM8K87qtw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1" + "Microsoft.Extensions.DependencyInjection": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "38Q8sEHwQ/+wVO/mwQBa0fcdHbezFpusHE+vBw/dSr6Fq/kzZm3H/NQX511Jki/R3FHd64IY559gdlHZQtYeEA==", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "Z6gBfpHqJsz2hGH+eUQUQI+DSHsDNhTKt8toHAtDhYFRlxUN1FKPKzNmTgSrAz1gtDTOBjDU5lQZ50463Ehw7A==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging.Configuration": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Logging": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "10.0.2", + "Microsoft.Extensions.Logging.Configuration": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2" } }, "Spectre.Console": { @@ -40,85 +40,85 @@ }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "njoRekyMIK+smav8B6KL2YgIfUtlsRNuT7wvurpLW+m/hoRKVnoELk2YxnUnWRGScCd1rukLMxShwLqEOKowDg==", + "resolved": "10.0.2", + "contentHash": "Lws+o4DFw6p5NquRoYA3d5QVvi49ugNw7TxbW4QGLsL8F1LCCyJqWFy0+RMQ/hzUuS9aKV5NJ/XGAF5N9/RQcQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", + "Microsoft.Extensions.Primitives": "10.0.2" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "kPlU11hql+L9RjrN2N9/0GcRcRcZrNFlLLjadasFWeBORT6pL6OE+RYRk90GGCyVGSxTK+e1/f3dsMj5zpFFiQ==", + "resolved": "10.0.2", + "contentHash": "KC5PslaTDnTuTvyke0KYAVBYdZ7IVTsU3JhHe69BpEbHLcj1YThP3bIGtZNOkZfast2AuLnul5lk4rZKxAdUGQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Primitives": "10.0.2" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "Lp4CZIuTVXtlvkAnTq6QvMSW7+H62gX2cU2vdFxHQUxvrWTpi7LwYI3X+YAyIS0r12/p7gaosco7efIxL4yFNw==", + "resolved": "10.0.2", + "contentHash": "/SdW50prUuenglSy7MXU3eVQkOk4/J4fjc+GIhv4NkTmaZOQyTqpVAYi8nRjNtOKHzCy7g5cSlOSgkbT7clLwQ==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.1", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1" + "Microsoft.Extensions.Configuration": "10.0.2", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.2" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", + "resolved": "10.0.2", + "contentHash": "J/Zmp6fY93JbaiZ11ckWvcyxMPjD6XVwIHQXBjryTBgn7O6O20HYg9uVLFcZlNfgH78MnreE/7EH+hjfzn7VyA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "Zg8LLnfZs5o2RCHD/+9NfDtJ40swauemwCa7sI8gQoAye/UJHRZNpCtC7a5XE7l9Z7mdI8iMWnLZ6m7Q6S3jLg==", + "resolved": "10.0.2", + "contentHash": "XVtNJfLZVTDmQS5RCUjIr7QEAgGhJ3yQ0L3PduN7rE4aijmqYl0pIF09ZSU8jgnxml91Mw59ze220g8S7anaOg==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.1", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Configuration.Binder": "10.0.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.1" + "Microsoft.Extensions.Configuration": "10.0.2", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", + "Microsoft.Extensions.Configuration.Binder": "10.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Logging": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.2" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", + "resolved": "10.0.2", + "contentHash": "1De2LJjmxdqopI5AYC5dIhoZQ79AR5ayywxNF1rXrXFtKQfbQOV9+n/IsZBa7qWlr0MqoGpW8+OY2v/57udZOA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Primitives": "10.0.2" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "pL78/Im7O3WmxHzlKUsWTYchKL881udU7E26gCD3T0+/tPhWVfjPwMzfN/MRKU7aoFYcOiqcG2k1QTlH5woWow==", + "resolved": "10.0.2", + "contentHash": "8njGDg0OdDBM4Zox0ybuUOJZkQ8HcH49F+POZBlG+nsfzEyqOCHyHEkWeRVI62qsssiugUVEKqUttT1ZbV0aJQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Configuration.Binder": "10.0.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", + "Microsoft.Extensions.Configuration.Binder": "10.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2", + "Microsoft.Extensions.Primitives": "10.0.2" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" + "resolved": "10.0.2", + "contentHash": "QmSiO+oLBEooGgB3i0GRXyeYRDHjllqt3k365jwfZlYWhvSHA3UL2NEVV5m8aZa041eIlblo6KMI5txvTMpTwA==" }, "bookgen.cli": { "type": "Project", "dependencies": { "BookGen.Vfs": "[1.0.0, )", - "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", - "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )" + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.2, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.2, )" } }, "bookgen.contents": { @@ -127,7 +127,7 @@ "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.2, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -136,17 +136,17 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" } }, "Webmaster442.WindowsTerminal": { diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index 1329cd17..c4e391d9 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -10,35 +10,35 @@ }, "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "a0EWuBs6D3d7XMGroDXm+WsAi5CVVfjOJvyxurzWnuhBN9CO+1qHKcrKV1JK7H/T4ZtHIoVCOX/YyWM8K87qtw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1" + "Microsoft.Extensions.DependencyInjection": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "38Q8sEHwQ/+wVO/mwQBa0fcdHbezFpusHE+vBw/dSr6Fq/kzZm3H/NQX511Jki/R3FHd64IY559gdlHZQtYeEA==", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "Z6gBfpHqJsz2hGH+eUQUQI+DSHsDNhTKt8toHAtDhYFRlxUN1FKPKzNmTgSrAz1gtDTOBjDU5lQZ50463Ehw7A==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging.Configuration": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Logging": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "10.0.2", + "Microsoft.Extensions.Logging.Configuration": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2" } }, "Microsoft.IO.RecyclableMemoryStream": { @@ -113,78 +113,78 @@ }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "njoRekyMIK+smav8B6KL2YgIfUtlsRNuT7wvurpLW+m/hoRKVnoELk2YxnUnWRGScCd1rukLMxShwLqEOKowDg==", + "resolved": "10.0.2", + "contentHash": "Lws+o4DFw6p5NquRoYA3d5QVvi49ugNw7TxbW4QGLsL8F1LCCyJqWFy0+RMQ/hzUuS9aKV5NJ/XGAF5N9/RQcQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", + "Microsoft.Extensions.Primitives": "10.0.2" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "kPlU11hql+L9RjrN2N9/0GcRcRcZrNFlLLjadasFWeBORT6pL6OE+RYRk90GGCyVGSxTK+e1/f3dsMj5zpFFiQ==", + "resolved": "10.0.2", + "contentHash": "KC5PslaTDnTuTvyke0KYAVBYdZ7IVTsU3JhHe69BpEbHLcj1YThP3bIGtZNOkZfast2AuLnul5lk4rZKxAdUGQ==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Primitives": "10.0.2" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "Lp4CZIuTVXtlvkAnTq6QvMSW7+H62gX2cU2vdFxHQUxvrWTpi7LwYI3X+YAyIS0r12/p7gaosco7efIxL4yFNw==", + "resolved": "10.0.2", + "contentHash": "/SdW50prUuenglSy7MXU3eVQkOk4/J4fjc+GIhv4NkTmaZOQyTqpVAYi8nRjNtOKHzCy7g5cSlOSgkbT7clLwQ==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.1", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1" + "Microsoft.Extensions.Configuration": "10.0.2", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.2" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", + "resolved": "10.0.2", + "contentHash": "J/Zmp6fY93JbaiZ11ckWvcyxMPjD6XVwIHQXBjryTBgn7O6O20HYg9uVLFcZlNfgH78MnreE/7EH+hjfzn7VyA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "Zg8LLnfZs5o2RCHD/+9NfDtJ40swauemwCa7sI8gQoAye/UJHRZNpCtC7a5XE7l9Z7mdI8iMWnLZ6m7Q6S3jLg==", + "resolved": "10.0.2", + "contentHash": "XVtNJfLZVTDmQS5RCUjIr7QEAgGhJ3yQ0L3PduN7rE4aijmqYl0pIF09ZSU8jgnxml91Mw59ze220g8S7anaOg==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.1", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Configuration.Binder": "10.0.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.1" + "Microsoft.Extensions.Configuration": "10.0.2", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", + "Microsoft.Extensions.Configuration.Binder": "10.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Logging": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.2" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", + "resolved": "10.0.2", + "contentHash": "1De2LJjmxdqopI5AYC5dIhoZQ79AR5ayywxNF1rXrXFtKQfbQOV9+n/IsZBa7qWlr0MqoGpW8+OY2v/57udZOA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Primitives": "10.0.2" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "pL78/Im7O3WmxHzlKUsWTYchKL881udU7E26gCD3T0+/tPhWVfjPwMzfN/MRKU7aoFYcOiqcG2k1QTlH5woWow==", + "resolved": "10.0.2", + "contentHash": "8njGDg0OdDBM4Zox0ybuUOJZkQ8HcH49F+POZBlG+nsfzEyqOCHyHEkWeRVI62qsssiugUVEKqUttT1ZbV0aJQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Configuration.Binder": "10.0.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", + "Microsoft.Extensions.Configuration.Binder": "10.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2", + "Microsoft.Extensions.Primitives": "10.0.2" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" + "resolved": "10.0.2", + "contentHash": "QmSiO+oLBEooGgB3i0GRXyeYRDHjllqt3k365jwfZlYWhvSHA3UL2NEVV5m8aZa041eIlblo6KMI5txvTMpTwA==" }, "Newtonsoft.Json": { "type": "Transitive", @@ -193,8 +193,8 @@ }, "ShimSkiaSharp": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "WyMf+0aj5IJcHkG/t89WApGFg756oXB30ehu9kYu9hjdfS0snkXHoAiTJZqVlqyLOvzC6eA+nOQ2hI84g//5Pg==" + "resolved": "3.4.1", + "contentHash": "ab3J5OGdwYLyXnom90OghS9NyRPH9dtsRtW5B3mL+F43YTlxido3nUtiR9NjB/sI3jvG/J744C29bsSBQ28AfQ==" }, "SkiaSharp.NativeAssets.macOS": { "type": "Transitive", @@ -203,27 +203,27 @@ }, "Svg.Custom": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "Wu0+Il+SZfY5+AT25b2PMlSbBrzGIo/6vMMMdOK8jd25I9tdJJbxOFJNG87gFgo9UY9OmpIFzVobGOHzFdJYeQ==", + "resolved": "3.4.1", + "contentHash": "ksHa7Zv8X1InxSWAeM0sSSvZM2W6FWgneXjU0bdYrQ20Q6q0nl3g0uaSWe53i/CC1OmR+/JAxsp8E3/wigtcng==", "dependencies": { "ExCSS": "4.3.1" } }, "Svg.Model": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "N4yWlvqv1XWZ+VvI29lT4HkEgtiP3rS/poMfWiVzz6Ao/3IjQCfmo2qrKphq77IqTdMYyh7wGDknMId7YrQY5Q==", + "resolved": "3.4.1", + "contentHash": "Zn04CWWIwV+qlJ4x4vg8FdCYNew5dRcsvkcCpzZggFKHGExIn4WTBtJMuLH8htuiLnbbgF6woIYpUll+seHfjw==", "dependencies": { - "ShimSkiaSharp": "3.2.1", - "Svg.Custom": "3.2.1" + "ShimSkiaSharp": "3.4.1", + "Svg.Custom": "3.4.1" } }, "bookgen.cli": { "type": "Project", "dependencies": { "BookGen.Vfs": "[1.0.0, )", - "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", - "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )" + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.2, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.2, )" } }, "bookgen.contents": { @@ -241,15 +241,15 @@ "SkiaSharp": "[3.119.1, )", "SkiaSharp.NativeAssets.Linux": "[3.119.1, )", "SkiaSharp.NativeAssets.Win32": "[3.119.1, )", - "Svg.Skia": "[3.2.1, )", - "System.ServiceModel.Syndication": "[10.0.1, )", + "Svg.Skia": "[3.4.1, )", + "System.ServiceModel.Syndication": "[10.0.2, )", "YamlDotNet": "[16.3.0, )" } }, "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.2, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -283,9 +283,9 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" }, "SkiaSharp": { "type": "CentralTransitive", @@ -311,20 +311,20 @@ }, "Svg.Skia": { "type": "CentralTransitive", - "requested": "[3.2.1, )", - "resolved": "3.2.1", - "contentHash": "pRJtMOc2hTcnxSu7cx6lf806S+2VrQ0eqUpGMBUQQ/pIIvzrtn0+lUjdABTvjr5rsdQF9OxE+r9npq2+3c6T4A==", + "requested": "[3.4.1, )", + "resolved": "3.4.1", + "contentHash": "fNGHIeIUtEDo41P4MYfVqjNL902t8EP5/tBY9vYDg8VbKSOD84ZlBfCL4KSxjf//HcJi1Uz4uZCUXuEAKJirCw==", "dependencies": { "SkiaSharp": "2.88.9", - "Svg.Custom": "3.2.1", - "Svg.Model": "3.2.1" + "Svg.Custom": "3.4.1", + "Svg.Model": "3.4.1" } }, "System.ServiceModel.Syndication": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "Z3+s66hOp7JVDOhkVk0gH/sjsshn99NUMiILcXLaA+3qe2OkLr3nB+3bRzXdfF6arlJBxGfkkki9GP6R9kvdFg==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "t28WU9anHmmRD5GHz5qBv/tokqDmU/Ksexz2DI6jAcoSX+p8VQ7rzHCxubDqK6baOtxEyza/oHECzBGi3rHyHQ==" }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index f155489c..b344021b 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -57,20 +57,20 @@ }, "Svg.Skia": { "type": "Direct", - "requested": "[3.2.1, )", - "resolved": "3.2.1", - "contentHash": "pRJtMOc2hTcnxSu7cx6lf806S+2VrQ0eqUpGMBUQQ/pIIvzrtn0+lUjdABTvjr5rsdQF9OxE+r9npq2+3c6T4A==", + "requested": "[3.4.1, )", + "resolved": "3.4.1", + "contentHash": "fNGHIeIUtEDo41P4MYfVqjNL902t8EP5/tBY9vYDg8VbKSOD84ZlBfCL4KSxjf//HcJi1Uz4uZCUXuEAKJirCw==", "dependencies": { "SkiaSharp": "2.88.9", - "Svg.Custom": "3.2.1", - "Svg.Model": "3.2.1" + "Svg.Custom": "3.4.1", + "Svg.Model": "3.4.1" } }, "System.ServiceModel.Syndication": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "Z3+s66hOp7JVDOhkVk0gH/sjsshn99NUMiILcXLaA+3qe2OkLr3nB+3bRzXdfF6arlJBxGfkkki9GP6R9kvdFg==" + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "t28WU9anHmmRD5GHz5qBv/tokqDmU/Ksexz2DI6jAcoSX+p8VQ7rzHCxubDqK6baOtxEyza/oHECzBGi3rHyHQ==" }, "YamlDotNet": { "type": "Direct", @@ -131,8 +131,8 @@ }, "ShimSkiaSharp": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "WyMf+0aj5IJcHkG/t89WApGFg756oXB30ehu9kYu9hjdfS0snkXHoAiTJZqVlqyLOvzC6eA+nOQ2hI84g//5Pg==" + "resolved": "3.4.1", + "contentHash": "ab3J5OGdwYLyXnom90OghS9NyRPH9dtsRtW5B3mL+F43YTlxido3nUtiR9NjB/sI3jvG/J744C29bsSBQ28AfQ==" }, "SkiaSharp.NativeAssets.macOS": { "type": "Transitive", @@ -141,19 +141,19 @@ }, "Svg.Custom": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "Wu0+Il+SZfY5+AT25b2PMlSbBrzGIo/6vMMMdOK8jd25I9tdJJbxOFJNG87gFgo9UY9OmpIFzVobGOHzFdJYeQ==", + "resolved": "3.4.1", + "contentHash": "ksHa7Zv8X1InxSWAeM0sSSvZM2W6FWgneXjU0bdYrQ20Q6q0nl3g0uaSWe53i/CC1OmR+/JAxsp8E3/wigtcng==", "dependencies": { "ExCSS": "4.3.1" } }, "Svg.Model": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "N4yWlvqv1XWZ+VvI29lT4HkEgtiP3rS/poMfWiVzz6Ao/3IjQCfmo2qrKphq77IqTdMYyh7wGDknMId7YrQY5Q==", + "resolved": "3.4.1", + "contentHash": "Zn04CWWIwV+qlJ4x4vg8FdCYNew5dRcsvkcCpzZggFKHGExIn4WTBtJMuLH8htuiLnbbgF6woIYpUll+seHfjw==", "dependencies": { - "ShimSkiaSharp": "3.2.1", - "Svg.Custom": "3.2.1" + "ShimSkiaSharp": "3.4.1", + "Svg.Custom": "3.4.1" } }, "bookgen.shell.shared": { From 2d1403d4d5869e582adb9c9d842bd761194f9254 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 6 Feb 2026 16:29:59 +0100 Subject: [PATCH 11/41] Config overlay --- Source/BookGen/BookGenArgumentBase.cs | 21 ++++++++++++++++++++- Source/BookGen/BuildCommandBase.cs | 4 ++-- Source/BookGen/Commands/LinksCommand.cs | 4 ++-- Source/BookGen/Commands/StatsCommand.cs | 4 ++-- Source/BookGen/Commands/ValidateCommand.cs | 4 ++-- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Source/BookGen/BookGenArgumentBase.cs b/Source/BookGen/BookGenArgumentBase.cs index 83126586..0c902746 100644 --- a/Source/BookGen/BookGenArgumentBase.cs +++ b/Source/BookGen/BookGenArgumentBase.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -17,8 +17,27 @@ public class BookGenArgumentBase : ArgumentsBase, IVerbosablityToggle [Switch("d", "dir")] public string Directory { get; set; } + [Switch("co", "configoverlay")] + public string ConfigOverlay { get; set; } = string.Empty; + public BookGenArgumentBase() { Directory = Environment.CurrentDirectory; } + + override public ValidationResult Validate(IValidationContext context) + { + if (!context.FileSystem.DirectoryExists(Directory)) + { + return ValidationResult.Error($"Directory '{Directory}' does not exist."); + } + + if (!string.IsNullOrEmpty(ConfigOverlay) + && !context.FileSystem.FileExists(ConfigOverlay)) + { + return ValidationResult.Error($"Config overlay file '{ConfigOverlay}' does not exist."); + } + + return ValidationResult.Ok(); + } } diff --git a/Source/BookGen/BuildCommandBase.cs b/Source/BookGen/BuildCommandBase.cs index b4e798f2..c0f4e1d8 100644 --- a/Source/BookGen/BuildCommandBase.cs +++ b/Source/BookGen/BuildCommandBase.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -47,7 +47,7 @@ public override async Task ExecuteAsync(BuildArguments arguments, IReadOnly _target.Scope = arguments.OutputDirectory; using var env = new BookEnvironment(_soruce, _target, _assetSource); - EnvironmentStatus status = await env.Initialize(); + EnvironmentStatus status = await env.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { diff --git a/Source/BookGen/Commands/LinksCommand.cs b/Source/BookGen/Commands/LinksCommand.cs index d29212b2..f377359c 100644 --- a/Source/BookGen/Commands/LinksCommand.cs +++ b/Source/BookGen/Commands/LinksCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -33,7 +33,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea _soruce.Scope = arguments.Directory; using var env = new BookEnvironment(_soruce, _soruce); - EnvironmentStatus status = await env.Initialize(); + EnvironmentStatus status = await env.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { diff --git a/Source/BookGen/Commands/StatsCommand.cs b/Source/BookGen/Commands/StatsCommand.cs index 9ed62e59..6060acc3 100644 --- a/Source/BookGen/Commands/StatsCommand.cs +++ b/Source/BookGen/Commands/StatsCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -35,7 +35,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea _soruce.Scope = arguments.Directory; using var env = new BookEnvironment(_soruce, _soruce); - var status = await env.Initialize(); + var status = await env.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { diff --git a/Source/BookGen/Commands/ValidateCommand.cs b/Source/BookGen/Commands/ValidateCommand.cs index 568a9918..5fd031c3 100644 --- a/Source/BookGen/Commands/ValidateCommand.cs +++ b/Source/BookGen/Commands/ValidateCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -31,7 +31,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea using var environment = new BookEnvironment(_writableFileSystem, _writableFileSystem); - EnvironmentStatus status = await environment.Initialize(); + EnvironmentStatus status = await environment.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { From a90ccc700c3f88c64080e12d9e8db39935ed089d Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 6 Feb 2026 16:30:48 +0100 Subject: [PATCH 12/41] Config overlay --- Source/BookGen.Vfs/JsonOptions.cs | 4 +- Source/Bookgen.Lib/BookEnvironment.cs | 16 +++- .../Bookgen.Lib/Confighandling/JsonMerger.cs | 84 +++++++++++++++++++ Test/Bookgen.Tests/Lib/UT_JsonMerger.cs | 82 ++++++++++++++++++ 4 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 Source/Bookgen.Lib/Confighandling/JsonMerger.cs create mode 100644 Test/Bookgen.Tests/Lib/UT_JsonMerger.cs diff --git a/Source/BookGen.Vfs/JsonOptions.cs b/Source/BookGen.Vfs/JsonOptions.cs index 9c0618d9..d6ae0798 100644 --- a/Source/BookGen.Vfs/JsonOptions.cs +++ b/Source/BookGen.Vfs/JsonOptions.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -13,7 +13,7 @@ namespace BookGen.Vfs; -internal static class JsonOptions +public static class JsonOptions { public readonly static JsonSerializerOptions SerializerOptions = new(JsonSerializerOptions.Default) { diff --git a/Source/Bookgen.Lib/BookEnvironment.cs b/Source/Bookgen.Lib/BookEnvironment.cs index 96f99460..0973e370 100644 --- a/Source/Bookgen.Lib/BookEnvironment.cs +++ b/Source/Bookgen.Lib/BookEnvironment.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -57,7 +57,7 @@ public void Dispose() } } - public async Task Initialize() + public async Task Initialize(string configOverlay) { if (_isInitialized) { @@ -87,7 +87,17 @@ public async Task Initialize() return status; } - Config? config = await _source.DeserializeAsync(FileNameConstants.ConfigFile); + var baseConfig = await _source.ReadJsonAsync(FileNameConstants.ConfigFile); + + JsonMerger configMerger = new JsonMerger(baseConfig); + + if (!string.IsNullOrEmpty(configOverlay)) + { + var overlayConfig = await _source.ReadJsonAsync(configOverlay); + configMerger.Merge(overlayConfig); + } + + Config? config = configMerger.Deserialize(); if (config == null) { diff --git a/Source/Bookgen.Lib/Confighandling/JsonMerger.cs b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs new file mode 100644 index 00000000..aaa37e21 --- /dev/null +++ b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs @@ -0,0 +1,84 @@ +using System.Text.Json; +using System.Text.Json.Nodes; + +//----------------------------------------------------------------------------- +// (c) 2019-2026 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using BookGen.Vfs; + +namespace Bookgen.Lib.Confighandling; + +internal sealed class JsonMerger +{ + private JsonObject _baseObject; + + public JsonMerger(JsonObject baseObject) + { + _baseObject = baseObject; + } + + private static JsonNode? Merge(JsonNode jsonBase, + JsonNode jsonMerge, + bool mergeIfAlreadyExists = true) + { + if (jsonBase == null || jsonMerge == null) + return jsonBase; + + switch (jsonBase) + { + case JsonObject jsonBaseObj when jsonMerge is JsonObject jsonMergeObj: + { + //NOTE: We must materialize the set (e.g. to an Array), and then clear the merge array so the node can then be + // re-assigned to the target/base Json; clearing the Object seems to be the most efficient approach... + var mergeNodesArray = jsonMergeObj.ToArray(); + jsonMergeObj.Clear(); + + foreach (var prop in mergeNodesArray) + { + if (mergeIfAlreadyExists || !jsonBaseObj.ContainsKey(prop.Key)) + jsonBaseObj[prop.Key] = jsonBaseObj[prop.Key] switch + { + JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj + => Merge(jsonBaseChildObj, jsonMergeChildObj), + JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray + => Merge(jsonBaseChildArray, jsonMergeChildArray), + _ => prop.Value + }; + } + break; + } + case JsonArray jsonBaseArray when jsonMerge is JsonArray jsonMergeArray: + { + //NOTE: We must materialize the set (e.g. to an Array), and then clear the merge array, + // so they can then be re-assigned to the target/base Json... + var mergeNodesArray = jsonMergeArray.ToArray(); + jsonMergeArray.Clear(); + foreach (var mergeNode in mergeNodesArray) jsonBaseArray.Add(mergeNode); + break; + } + default: + throw new ArgumentException($"The JsonNode type [{jsonBase.GetType().Name}] is incompatible for merging with the target/base " + + $"type [{jsonMerge.GetType().Name}]; merge requires the types to be the same."); + + } + + return jsonBase; + } + + + public void Merge(JsonObject overlay) + { + var result = Merge(_baseObject, overlay); + if (result is JsonObject mergedObj) + { + _baseObject = mergedObj; + return; + } + throw new InvalidOperationException("Merging resulted in invalid object"); + } + + public T? Deserialize() + => JsonSerializer.Deserialize(_baseObject, JsonOptions.SerializerOptions); +} diff --git a/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs b/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs new file mode 100644 index 00000000..6c7ad7ce --- /dev/null +++ b/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2026 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using System.Text.Json; +using System.Text.Json.Nodes; + +using Bookgen.Lib.Confighandling; +using Bookgen.Lib.Domain.IO.Configuration; + +namespace Bookgen.Tests.Lib; + +[TestFixture] +public class UT_JsonMerger +{ + [Test] + public void EnsureThat_Merge_Works() + { + var config1 = new Config + { + Book2LetterISO639Language = "en", + BookAuthor = "Author One", + PrintConfig = new PrintConfig + { + Images = new ImageConfig + { + ImageQualityOnResize = 70, + ResizeAndRecodeImages = ImgRecodeOption.AsPng, + SvgRecode = SvgRecodeOption.AsPng, + } + }, + WordpressConfig = new WordpressConfig + { + CssClasses = new CssClasses + { + H1 = "custom-h1", + H2 = "custom-h2", + } + }, + }; + + var overlay = """ + { + "BookAuthor": "Author Two", + "PrintConfig": { + "Images": { + "ImageQualityOnResize": 85 + } + }, + "WordpressConfig": { + "CssClasses": { + "H2": "overridden-h2", + "H3": "custom-h3" + } + } + } + """; + + var node1 = JsonSerializer.SerializeToNode(config1) as JsonObject; + var nodeOverlay = JsonNode.Parse(overlay) as JsonObject; + + JsonMerger sut = new JsonMerger(node1!); + sut.Merge(nodeOverlay!); + + var result = sut.Deserialize(); + + Assert.That(result, Is.Not.Null); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result!.Book2LetterISO639Language, Is.EqualTo("en")); + Assert.That(result.BookAuthor, Is.EqualTo("Author Two")); + Assert.That(result.PrintConfig.Images.ImageQualityOnResize, Is.EqualTo(85)); + Assert.That(result.PrintConfig.Images.ResizeAndRecodeImages, Is.EqualTo(ImgRecodeOption.AsPng)); + Assert.That(result.PrintConfig.Images.SvgRecode, Is.EqualTo(SvgRecodeOption.AsPng)); + Assert.That(result.WordpressConfig.CssClasses.H1, Is.EqualTo("custom-h1")); + Assert.That(result.WordpressConfig.CssClasses.H2, Is.EqualTo("overridden-h2")); + Assert.That(result.WordpressConfig.CssClasses.H3, Is.EqualTo("custom-h3")); + } + } +} From 6ac7bfbfecc91fc6d2639f37212fb59e2df49a3e Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 6 Feb 2026 16:35:34 +0100 Subject: [PATCH 13/41] Code formatting --- Source/BookGen/Tooldownloaders/GlowDownloader.cs | 2 +- Source/BookGen/Tooldownloaders/ToolDownloadUi.cs | 2 +- Source/Bookgen.Lib/Confighandling/JsonMerger.cs | 4 ++-- .../Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs | 4 ++-- .../Markdown/Renderers/Terminal/CodeBlockRenderer.cs | 6 +++--- .../Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs | 2 +- .../Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs | 4 ++-- .../Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs | 2 +- .../Markdown/Renderers/Terminal/LinkInlineRenderer.cs | 2 +- .../Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs | 2 -- .../Markdown/Renderers/Terminal/LiteralInlineRenderer.cs | 2 +- .../Markdown/Renderers/Terminal/QuoteBlockRenderer.cs | 2 +- .../Markdown/Renderers/Terminal/RenderOptions.cs | 2 +- 13 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Source/BookGen/Tooldownloaders/GlowDownloader.cs b/Source/BookGen/Tooldownloaders/GlowDownloader.cs index 8bcecb3f..985b11f1 100644 --- a/Source/BookGen/Tooldownloaders/GlowDownloader.cs +++ b/Source/BookGen/Tooldownloaders/GlowDownloader.cs @@ -15,7 +15,7 @@ namespace BookGen.Tooldownloaders; internal sealed class GlowDownloader : TooldownloaderBase { - public GlowDownloader(IApiClient apiClient, + public GlowDownloader(IApiClient apiClient, RecyclableMemoryStreamManager memoryStreamManager, ILogger log) : base(apiClient, memoryStreamManager, log) diff --git a/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs b/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs index b5f6908f..0bd06bd2 100644 --- a/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs +++ b/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs @@ -11,7 +11,7 @@ namespace BookGen.Tooldownloaders; -internal class ToolDownloadUi : IDownloadUi +internal sealed class ToolDownloadUi : IDownloadUi { private class ExtendedProgresBar : Progressbar { diff --git a/Source/Bookgen.Lib/Confighandling/JsonMerger.cs b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs index aaa37e21..e8827c8d 100644 --- a/Source/Bookgen.Lib/Confighandling/JsonMerger.cs +++ b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs @@ -40,7 +40,7 @@ public JsonMerger(JsonObject baseObject) if (mergeIfAlreadyExists || !jsonBaseObj.ContainsKey(prop.Key)) jsonBaseObj[prop.Key] = jsonBaseObj[prop.Key] switch { - JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj + JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj => Merge(jsonBaseChildObj, jsonMergeChildObj), JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray => Merge(jsonBaseChildArray, jsonMergeChildArray), @@ -79,6 +79,6 @@ public void Merge(JsonObject overlay) throw new InvalidOperationException("Merging resulted in invalid object"); } - public T? Deserialize() + public T? Deserialize() => JsonSerializer.Deserialize(_baseObject, JsonOptions.SerializerOptions); } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs index 9fe9d22c..cae7de68 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs @@ -1,6 +1,6 @@ -using Markdig.Syntax.Inlines; +using System.Web; -using System.Web; +using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs index a6c95b0c..87559381 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs @@ -17,12 +17,12 @@ protected override void Write(TerminalRenderer renderer, CodeBlock obj) renderer.Write(begin); - for (int i=0; i < obj.Lines.Count; i++) + for (int i = 0; i < obj.Lines.Count; i++) { StringLine codeLine = obj.Lines.Lines[i]; if (!string.IsNullOrWhiteSpace(codeLine.ToString())) { - if (i == obj.Lines.Count - 1) + if (i == obj.Lines.Count - 1) { renderer.Write(codeLine.ToString()).WriteReset().WriteLine(); } @@ -37,4 +37,4 @@ protected override void Write(TerminalRenderer renderer, CodeBlock obj) renderer.WriteLine(); } } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs index e9c1609d..713c6776 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs @@ -9,4 +9,4 @@ protected override void Write(TerminalRenderer renderer, DelimiterInline obj) renderer.Write(obj.ToLiteral()); renderer.WriteChildren(obj); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs index 7a52ceca..842fe39e 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs @@ -1,6 +1,6 @@ -using Markdig.Syntax.Inlines; +using System.Diagnostics; -using System.Diagnostics; +using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs index ee1f1fe7..928779d1 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs @@ -12,4 +12,4 @@ protected override void Write(TerminalRenderer renderer, LineBreakInline obj) } renderer.EnsureLine(); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs index 02e6e93b..9fc5dde2 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs @@ -31,4 +31,4 @@ protected override void Write(TerminalRenderer renderer, LinkInline obj) renderer.Write(text); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs index 2c735c58..0ae9bb82 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs @@ -1,7 +1,5 @@ using Markdig.Syntax; -using System.Security.Cryptography.X509Certificates; - namespace Bookgen.Lib.Markdown.Renderers.Terminal; internal sealed class ListRenderer : TerminalObjectRenderer diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs index e03e404d..56fadc82 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs @@ -9,4 +9,4 @@ protected override void Write(TerminalRenderer renderer, LiteralInline obj) string content = obj.Content.ToString(); renderer.Write(content); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs index 4a4df4c9..8debad31 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs @@ -19,4 +19,4 @@ protected override void Write(TerminalRenderer renderer, QuoteBlock obj) .WriteReset(); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs index c7f0f2b9..a0953288 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs @@ -11,7 +11,7 @@ public sealed class RenderOptions public TerminalColor CodeInlineColor { get; set; } public TerminalColor QuoteBlockColor { get; set; } - + public TerminalColor CodeBlockBackground { get; set; } public TerminalColor CodeBlockColor { get; set; } From 5aadc4130272e58de4b494306801008c58ef7214 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 6 Feb 2026 16:35:34 +0100 Subject: [PATCH 14/41] Code formatting --- .editorconfig | 98 +++++++++---------- .../BookGen/Tooldownloaders/GlowDownloader.cs | 2 +- .../BookGen/Tooldownloaders/ToolDownloadUi.cs | 2 +- .../Bookgen.Lib/Confighandling/JsonMerger.cs | 4 +- .../Terminal/AutolinkInlineRenderer.cs | 4 +- .../Renderers/Terminal/CodeBlockRenderer.cs | 6 +- .../Terminal/DelimiterInlineRenderer.cs | 2 +- .../Terminal/EmphasisInlineRenderer.cs | 4 +- .../Terminal/LineBreakInlineRenderer.cs | 2 +- .../Renderers/Terminal/LinkInlineRenderer.cs | 2 +- .../Renderers/Terminal/ListRenderer.cs | 2 - .../Terminal/LiteralInlineRenderer.cs | 2 +- .../Renderers/Terminal/QuoteBlockRenderer.cs | 2 +- .../Renderers/Terminal/RenderOptions.cs | 2 +- 14 files changed, 65 insertions(+), 69 deletions(-) diff --git a/.editorconfig b/.editorconfig index 473207c5..477a603e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,12 +3,22 @@ root = true # All files [*] indent_style = space -dotnet_diagnostic.CA1047.severity = error # Xml files [*.xml] indent_size = 2 +# Xml project files +[*.{csproj,fsproj,vbproj,proj,slnx}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +[*.json] +indent_size = 2 + # C# files [*.cs] @@ -19,7 +29,6 @@ indent_size = 4 tab_width = 4 # New line preferences -end_of_line = crlf insert_final_newline = false #### .NET Coding Conventions #### @@ -53,13 +62,16 @@ dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent dotnet_style_coalesce_expression = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_object_initializer = true:suggestion dotnet_style_operator_placement_when_wrapping = beginning_of_line dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion dotnet_style_prefer_compound_assignment = true:suggestion dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = false:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion @@ -79,15 +91,15 @@ dotnet_remove_unnecessary_suppression_exclusions = none [*.cs] # var preferences -csharp_style_var_elsewhere = false:silent +csharp_style_var_elsewhere = false:suggestion csharp_style_var_for_built_in_types = false:silent -csharp_style_var_when_type_is_apparent = false:silent +csharp_style_var_when_type_is_apparent = true:suggestion # Expression-bodied members csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_lambdas = when_on_single_line:error +csharp_style_expression_bodied_lambdas = true:suggestion csharp_style_expression_bodied_local_functions = false:silent csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_operators = false:silent @@ -96,6 +108,7 @@ csharp_style_expression_bodied_properties = true:silent # Pattern matching preferences csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion csharp_style_prefer_not_pattern = true:suggestion csharp_style_prefer_pattern_matching = true:silent csharp_style_prefer_switch_expression = true:suggestion @@ -104,20 +117,31 @@ csharp_style_prefer_switch_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences +csharp_prefer_static_anonymous_function = true:suggestion csharp_prefer_static_local_function = true:warning -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent +csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion # Code-block preferences csharp_prefer_braces = true:silent csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent # Expression-level preferences csharp_prefer_simple_default_expression = true:suggestion csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion -csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion csharp_style_throw_expression = true:suggestion csharp_style_unused_value_assignment_preference = discard_variable:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent @@ -171,32 +195,21 @@ csharp_space_between_square_brackets = false # Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true -csharp_style_namespace_declarations = file_scoped:error -csharp_style_prefer_method_group_conversion = true:silent -csharp_style_prefer_top_level_statements = true:silent -csharp_style_prefer_primary_constructors = false:suggestion -csharp_style_prefer_null_check_over_type_check = true:suggestion -csharp_style_prefer_local_over_anonymous_function = true:suggestion -csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion -csharp_style_prefer_tuple_swap = true:suggestion -csharp_style_prefer_utf8_string_literals = true:suggestion -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent -csharp_style_prefer_readonly_struct = true:suggestion -csharp_style_prefer_readonly_struct_member = true:suggestion -dotnet_diagnostic.CA1070.severity = error -dotnet_diagnostic.CA1032.severity = error -dotnet_diagnostic.CA1865.severity = error -dotnet_diagnostic.CA1866.severity = error -dotnet_diagnostic.CA1867.severity = error -dotnet_diagnostic.CA2014.severity = error csharp_prefer_system_threading_lock = true:suggestion -csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion -csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion csharp_style_prefer_simple_property_accessors = true:suggestion +csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion +csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +dotnet_diagnostic.CA1003.severity = error +dotnet_diagnostic.CA1008.severity = error +dotnet_diagnostic.CA1032.severity = error +dotnet_diagnostic.CA1034.severity = silent +dotnet_diagnostic.CA1036.severity = error +dotnet_diagnostic.CA1044.severity = error #### Naming styles #### [*.{cs,vb}] @@ -388,32 +401,17 @@ dotnet_naming_style.s_camelcase.required_prefix = s_ dotnet_naming_style.s_camelcase.required_suffix = dotnet_naming_style.s_camelcase.word_separator = dotnet_naming_style.s_camelcase.capitalization = camel_case -dotnet_style_prefer_collection_expression = when_types_exactly_match:suggestion -dotnet_style_namespace_match_folder = true:error tab_width = 4 indent_size = 4 end_of_line = crlf -dotnet_diagnostic.CA1000.severity = silent -dotnet_diagnostic.CA1001.severity = warning -dotnet_diagnostic.CA1002.severity = suggestion -dotnet_diagnostic.CA1008.severity = error -dotnet_diagnostic.CA1061.severity = error -dotnet_diagnostic.CA1063.severity = error -dotnet_diagnostic.CA2000.severity = error -dotnet_diagnostic.CA1816.severity = error -dotnet_diagnostic.CA2213.severity = error -dotnet_diagnostic.CA2215.severity = error -dotnet_diagnostic.CA1064.severity = error -dotnet_diagnostic.CA1069.severity = error -dotnet_diagnostic.CA1401.severity = error -dotnet_diagnostic.CA1501.severity = error -dotnet_diagnostic.CA1700.severity = error -dotnet_diagnostic.CA1821.severity = error -dotnet_diagnostic.CA1836.severity = error -dotnet_diagnostic.CA1843.severity = error -dotnet_diagnostic.CA1842.severity = error -dotnet_diagnostic.CA2011.severity = error dotnet_style_allow_multiple_blank_lines_experimental = true:silent dotnet_style_allow_statement_immediately_after_block_experimental = true:silent insert_final_newline = true +dotnet_diagnostic.CA1001.severity = error +dotnet_diagnostic.CA1002.severity = silent +dotnet_diagnostic.CA1005.severity = error +dotnet_diagnostic.CA1010.severity = error +dotnet_diagnostic.CA1041.severity = error +dotnet_diagnostic.CA2025.severity = error +dotnet_diagnostic.CA2213.severity = error diff --git a/Source/BookGen/Tooldownloaders/GlowDownloader.cs b/Source/BookGen/Tooldownloaders/GlowDownloader.cs index 8bcecb3f..985b11f1 100644 --- a/Source/BookGen/Tooldownloaders/GlowDownloader.cs +++ b/Source/BookGen/Tooldownloaders/GlowDownloader.cs @@ -15,7 +15,7 @@ namespace BookGen.Tooldownloaders; internal sealed class GlowDownloader : TooldownloaderBase { - public GlowDownloader(IApiClient apiClient, + public GlowDownloader(IApiClient apiClient, RecyclableMemoryStreamManager memoryStreamManager, ILogger log) : base(apiClient, memoryStreamManager, log) diff --git a/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs b/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs index b5f6908f..0bd06bd2 100644 --- a/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs +++ b/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs @@ -11,7 +11,7 @@ namespace BookGen.Tooldownloaders; -internal class ToolDownloadUi : IDownloadUi +internal sealed class ToolDownloadUi : IDownloadUi { private class ExtendedProgresBar : Progressbar { diff --git a/Source/Bookgen.Lib/Confighandling/JsonMerger.cs b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs index aaa37e21..e8827c8d 100644 --- a/Source/Bookgen.Lib/Confighandling/JsonMerger.cs +++ b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs @@ -40,7 +40,7 @@ public JsonMerger(JsonObject baseObject) if (mergeIfAlreadyExists || !jsonBaseObj.ContainsKey(prop.Key)) jsonBaseObj[prop.Key] = jsonBaseObj[prop.Key] switch { - JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj + JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj => Merge(jsonBaseChildObj, jsonMergeChildObj), JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray => Merge(jsonBaseChildArray, jsonMergeChildArray), @@ -79,6 +79,6 @@ public void Merge(JsonObject overlay) throw new InvalidOperationException("Merging resulted in invalid object"); } - public T? Deserialize() + public T? Deserialize() => JsonSerializer.Deserialize(_baseObject, JsonOptions.SerializerOptions); } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs index 9fe9d22c..cae7de68 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs @@ -1,6 +1,6 @@ -using Markdig.Syntax.Inlines; +using System.Web; -using System.Web; +using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs index a6c95b0c..87559381 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs @@ -17,12 +17,12 @@ protected override void Write(TerminalRenderer renderer, CodeBlock obj) renderer.Write(begin); - for (int i=0; i < obj.Lines.Count; i++) + for (int i = 0; i < obj.Lines.Count; i++) { StringLine codeLine = obj.Lines.Lines[i]; if (!string.IsNullOrWhiteSpace(codeLine.ToString())) { - if (i == obj.Lines.Count - 1) + if (i == obj.Lines.Count - 1) { renderer.Write(codeLine.ToString()).WriteReset().WriteLine(); } @@ -37,4 +37,4 @@ protected override void Write(TerminalRenderer renderer, CodeBlock obj) renderer.WriteLine(); } } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs index e9c1609d..713c6776 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs @@ -9,4 +9,4 @@ protected override void Write(TerminalRenderer renderer, DelimiterInline obj) renderer.Write(obj.ToLiteral()); renderer.WriteChildren(obj); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs index 7a52ceca..842fe39e 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs @@ -1,6 +1,6 @@ -using Markdig.Syntax.Inlines; +using System.Diagnostics; -using System.Diagnostics; +using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs index ee1f1fe7..928779d1 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs @@ -12,4 +12,4 @@ protected override void Write(TerminalRenderer renderer, LineBreakInline obj) } renderer.EnsureLine(); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs index 02e6e93b..9fc5dde2 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs @@ -31,4 +31,4 @@ protected override void Write(TerminalRenderer renderer, LinkInline obj) renderer.Write(text); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs index 2c735c58..0ae9bb82 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs @@ -1,7 +1,5 @@ using Markdig.Syntax; -using System.Security.Cryptography.X509Certificates; - namespace Bookgen.Lib.Markdown.Renderers.Terminal; internal sealed class ListRenderer : TerminalObjectRenderer diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs index e03e404d..56fadc82 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs @@ -9,4 +9,4 @@ protected override void Write(TerminalRenderer renderer, LiteralInline obj) string content = obj.Content.ToString(); renderer.Write(content); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs index 4a4df4c9..8debad31 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs @@ -19,4 +19,4 @@ protected override void Write(TerminalRenderer renderer, QuoteBlock obj) .WriteReset(); } -} \ No newline at end of file +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs index c7f0f2b9..a0953288 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs @@ -11,7 +11,7 @@ public sealed class RenderOptions public TerminalColor CodeInlineColor { get; set; } public TerminalColor QuoteBlockColor { get; set; } - + public TerminalColor CodeBlockBackground { get; set; } public TerminalColor CodeBlockColor { get; set; } From e0f4cbafc0a9c485d0dc0ec60a6700412e697a1d Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sat, 14 Feb 2026 15:55:19 +0100 Subject: [PATCH 15/41] Updated dependences --- Directory.Packages.props | 18 +- Source/BookGen.Cli/packages.lock.json | 14 +- .../BookGen.Shell.Shared/packages.lock.json | 14 +- Source/BookGen.Shellprog/packages.lock.json | 122 ++++++------- Source/BookGen/packages.lock.json | 170 +++++++++--------- Source/Bookgen.Lib/packages.lock.json | 38 ++-- 6 files changed, 188 insertions(+), 188 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 30d8fc8e..aec8df1b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,27 +5,27 @@ - + - - - - + + + + - - - + + + - + diff --git a/Source/BookGen.Cli/packages.lock.json b/Source/BookGen.Cli/packages.lock.json index d09f29db..f260753c 100644 --- a/Source/BookGen.Cli/packages.lock.json +++ b/Source/BookGen.Cli/packages.lock.json @@ -4,17 +4,17 @@ "net10.0": { "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "bookgen.vfs": { diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json index 999b8ee4..e18b60dc 100644 --- a/Source/BookGen.Shell.Shared/packages.lock.json +++ b/Source/BookGen.Shell.Shared/packages.lock.json @@ -4,11 +4,11 @@ "net10.0": { "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Webmaster442.WindowsTerminal": { @@ -19,9 +19,9 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" } } } diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json index f9ce9d67..0a12cd5d 100644 --- a/Source/BookGen.Shellprog/packages.lock.json +++ b/Source/BookGen.Shellprog/packages.lock.json @@ -4,26 +4,26 @@ "net10.0": { "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "a0EWuBs6D3d7XMGroDXm+WsAi5CVVfjOJvyxurzWnuhBN9CO+1qHKcrKV1JK7H/T4ZtHIoVCOX/YyWM8K87qtw==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.2", - "Microsoft.Extensions.Logging.Abstractions": "10.0.2", - "Microsoft.Extensions.Options": "10.0.2" + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "Z6gBfpHqJsz2hGH+eUQUQI+DSHsDNhTKt8toHAtDhYFRlxUN1FKPKzNmTgSrAz1gtDTOBjDU5lQZ50463Ehw7A==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "7sRvbBH3icaV9qil8fyBKmR+yEZ0yDU6Bq/KgBwswS36164yGaxbf7Kd4hD1iHZ2GfvyoJWWqBUBm9QX/IASAQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", - "Microsoft.Extensions.Logging": "10.0.2", - "Microsoft.Extensions.Logging.Abstractions": "10.0.2", - "Microsoft.Extensions.Logging.Configuration": "10.0.2", - "Microsoft.Extensions.Options": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Configuration": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" } }, "Spectre.Console": { @@ -40,85 +40,85 @@ }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "Lws+o4DFw6p5NquRoYA3d5QVvi49ugNw7TxbW4QGLsL8F1LCCyJqWFy0+RMQ/hzUuS9aKV5NJ/XGAF5N9/RQcQ==", + "resolved": "10.0.3", + "contentHash": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", - "Microsoft.Extensions.Primitives": "10.0.2" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "KC5PslaTDnTuTvyke0KYAVBYdZ7IVTsU3JhHe69BpEbHLcj1YThP3bIGtZNOkZfast2AuLnul5lk4rZKxAdUGQ==", + "resolved": "10.0.3", + "contentHash": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.2" + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "/SdW50prUuenglSy7MXU3eVQkOk4/J4fjc+GIhv4NkTmaZOQyTqpVAYi8nRjNtOKHzCy7g5cSlOSgkbT7clLwQ==", + "resolved": "10.0.3", + "contentHash": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.2", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.2" + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "J/Zmp6fY93JbaiZ11ckWvcyxMPjD6XVwIHQXBjryTBgn7O6O20HYg9uVLFcZlNfgH78MnreE/7EH+hjfzn7VyA==", + "resolved": "10.0.3", + "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "XVtNJfLZVTDmQS5RCUjIr7QEAgGhJ3yQ0L3PduN7rE4aijmqYl0pIF09ZSU8jgnxml91Mw59ze220g8S7anaOg==", + "resolved": "10.0.3", + "contentHash": "PBlaoYeusaxNYyN4WFjzcXWlUDSvLUPxy/e6oP1SONOOYA/oBWT2uBmFGJMV9VTtXiXXxCB39LqlYWbsWE4UKA==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.2", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", - "Microsoft.Extensions.Configuration.Binder": "10.0.2", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", - "Microsoft.Extensions.Logging": "10.0.2", - "Microsoft.Extensions.Logging.Abstractions": "10.0.2", - "Microsoft.Extensions.Options": "10.0.2", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.2" + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.3" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "1De2LJjmxdqopI5AYC5dIhoZQ79AR5ayywxNF1rXrXFtKQfbQOV9+n/IsZBa7qWlr0MqoGpW8+OY2v/57udZOA==", + "resolved": "10.0.3", + "contentHash": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", - "Microsoft.Extensions.Primitives": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "8njGDg0OdDBM4Zox0ybuUOJZkQ8HcH49F+POZBlG+nsfzEyqOCHyHEkWeRVI62qsssiugUVEKqUttT1ZbV0aJQ==", + "resolved": "10.0.3", + "contentHash": "bn6QoBbbvwmzLIFyxrnL2/e+sqoNUOGbHyfWK9DPONMv1mDCYHm/C7MusYASM31b2lUx6OiDmonb3v+dv5t0nA==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", - "Microsoft.Extensions.Configuration.Binder": "10.0.2", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", - "Microsoft.Extensions.Options": "10.0.2", - "Microsoft.Extensions.Primitives": "10.0.2" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "QmSiO+oLBEooGgB3i0GRXyeYRDHjllqt3k365jwfZlYWhvSHA3UL2NEVV5m8aZa041eIlblo6KMI5txvTMpTwA==" + "resolved": "10.0.3", + "contentHash": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==" }, "bookgen.cli": { "type": "Project", "dependencies": { "BookGen.Vfs": "[1.0.0, )", - "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.2, )", - "Microsoft.Extensions.Logging.Abstractions": "[10.0.2, )" + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.3, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )" } }, "bookgen.contents": { @@ -127,7 +127,7 @@ "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[10.0.2, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -136,17 +136,17 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Webmaster442.WindowsTerminal": { diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index c4e391d9..92ab81e4 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -4,41 +4,41 @@ "net10.0": { "Markdig": { "type": "Direct", - "requested": "[0.44.0, )", - "resolved": "0.44.0", - "contentHash": "X+CYMjcUnh/yO24wOSQxVFLiGqWrrtXJ5M7toHiM1Zk4Fg9UMLN5fkaq6FSOWH+mIprsHHgDMlq3MJhmrXalhg==" + "requested": "[0.45.0, )", + "resolved": "0.45.0", + "contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg==" }, "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "a0EWuBs6D3d7XMGroDXm+WsAi5CVVfjOJvyxurzWnuhBN9CO+1qHKcrKV1JK7H/T4ZtHIoVCOX/YyWM8K87qtw==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.2", - "Microsoft.Extensions.Logging.Abstractions": "10.0.2", - "Microsoft.Extensions.Options": "10.0.2" + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "Z6gBfpHqJsz2hGH+eUQUQI+DSHsDNhTKt8toHAtDhYFRlxUN1FKPKzNmTgSrAz1gtDTOBjDU5lQZ50463Ehw7A==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "7sRvbBH3icaV9qil8fyBKmR+yEZ0yDU6Bq/KgBwswS36164yGaxbf7Kd4hD1iHZ2GfvyoJWWqBUBm9QX/IASAQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", - "Microsoft.Extensions.Logging": "10.0.2", - "Microsoft.Extensions.Logging.Abstractions": "10.0.2", - "Microsoft.Extensions.Logging.Configuration": "10.0.2", - "Microsoft.Extensions.Options": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Configuration": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" } }, "Microsoft.IO.RecyclableMemoryStream": { @@ -113,78 +113,78 @@ }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "Lws+o4DFw6p5NquRoYA3d5QVvi49ugNw7TxbW4QGLsL8F1LCCyJqWFy0+RMQ/hzUuS9aKV5NJ/XGAF5N9/RQcQ==", + "resolved": "10.0.3", + "contentHash": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", - "Microsoft.Extensions.Primitives": "10.0.2" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "KC5PslaTDnTuTvyke0KYAVBYdZ7IVTsU3JhHe69BpEbHLcj1YThP3bIGtZNOkZfast2AuLnul5lk4rZKxAdUGQ==", + "resolved": "10.0.3", + "contentHash": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.2" + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "/SdW50prUuenglSy7MXU3eVQkOk4/J4fjc+GIhv4NkTmaZOQyTqpVAYi8nRjNtOKHzCy7g5cSlOSgkbT7clLwQ==", + "resolved": "10.0.3", + "contentHash": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.2", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.2" + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "J/Zmp6fY93JbaiZ11ckWvcyxMPjD6XVwIHQXBjryTBgn7O6O20HYg9uVLFcZlNfgH78MnreE/7EH+hjfzn7VyA==", + "resolved": "10.0.3", + "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "XVtNJfLZVTDmQS5RCUjIr7QEAgGhJ3yQ0L3PduN7rE4aijmqYl0pIF09ZSU8jgnxml91Mw59ze220g8S7anaOg==", + "resolved": "10.0.3", + "contentHash": "PBlaoYeusaxNYyN4WFjzcXWlUDSvLUPxy/e6oP1SONOOYA/oBWT2uBmFGJMV9VTtXiXXxCB39LqlYWbsWE4UKA==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.2", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", - "Microsoft.Extensions.Configuration.Binder": "10.0.2", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", - "Microsoft.Extensions.Logging": "10.0.2", - "Microsoft.Extensions.Logging.Abstractions": "10.0.2", - "Microsoft.Extensions.Options": "10.0.2", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.2" + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.3" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "1De2LJjmxdqopI5AYC5dIhoZQ79AR5ayywxNF1rXrXFtKQfbQOV9+n/IsZBa7qWlr0MqoGpW8+OY2v/57udZOA==", + "resolved": "10.0.3", + "contentHash": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", - "Microsoft.Extensions.Primitives": "10.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "8njGDg0OdDBM4Zox0ybuUOJZkQ8HcH49F+POZBlG+nsfzEyqOCHyHEkWeRVI62qsssiugUVEKqUttT1ZbV0aJQ==", + "resolved": "10.0.3", + "contentHash": "bn6QoBbbvwmzLIFyxrnL2/e+sqoNUOGbHyfWK9DPONMv1mDCYHm/C7MusYASM31b2lUx6OiDmonb3v+dv5t0nA==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.2", - "Microsoft.Extensions.Configuration.Binder": "10.0.2", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", - "Microsoft.Extensions.Options": "10.0.2", - "Microsoft.Extensions.Primitives": "10.0.2" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.2", - "contentHash": "QmSiO+oLBEooGgB3i0GRXyeYRDHjllqt3k365jwfZlYWhvSHA3UL2NEVV5m8aZa041eIlblo6KMI5txvTMpTwA==" + "resolved": "10.0.3", + "contentHash": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==" }, "Newtonsoft.Json": { "type": "Transitive", @@ -198,8 +198,8 @@ }, "SkiaSharp.NativeAssets.macOS": { "type": "Transitive", - "resolved": "3.119.1", - "contentHash": "6hR3BdLhApjDxR1bFrJ7/lMydPfI01s3K+3WjIXFUlfC0MFCFCwRzv+JtzIkW9bDXs7XUVQS+6EVf0uzCasnGQ==" + "resolved": "3.119.2", + "contentHash": "I2jMGQ/26KOnc6iAoR+Mxh9vSJJ2vioJyj9aJ9OL5yEZyXothXJxf4vBMqnSaiXMqiiU1scG7KqtT0CLkmMmWA==" }, "Svg.Custom": { "type": "Transitive", @@ -222,8 +222,8 @@ "type": "Project", "dependencies": { "BookGen.Vfs": "[1.0.0, )", - "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.2, )", - "Microsoft.Extensions.Logging.Abstractions": "[10.0.2, )" + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.3, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )" } }, "bookgen.contents": { @@ -234,22 +234,22 @@ "dependencies": { "BookGen.Shell.Shared": "[1.0.0, )", "BookGen.Vfs": "[1.0.0, )", - "Markdig": "[0.44.0, )", + "Markdig": "[0.45.0, )", "Microsoft.ClearScript": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.linux-x64": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.win-x64": "[7.5.0, )", - "SkiaSharp": "[3.119.1, )", - "SkiaSharp.NativeAssets.Linux": "[3.119.1, )", - "SkiaSharp.NativeAssets.Win32": "[3.119.1, )", + "SkiaSharp": "[3.119.2, )", + "SkiaSharp.NativeAssets.Linux": "[3.119.2, )", + "SkiaSharp.NativeAssets.Win32": "[3.119.2, )", "Svg.Skia": "[3.4.1, )", - "System.ServiceModel.Syndication": "[10.0.2, )", + "System.ServiceModel.Syndication": "[10.0.3, )", "YamlDotNet": "[16.3.0, )" } }, "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[10.0.2, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -283,31 +283,31 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" }, "SkiaSharp": { "type": "CentralTransitive", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "+Ru1BTSZQne3Vp+vbSb50Ke3Nlc3ZnItxx4+751J9WZ8YzLKAV/n+9DAo4zFTyeCI//ueT63c+VybmTTpYBEiw==", + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "nmy2dOFWPvQKMglfpjz8+/xQQcSrL9jzul3cUyzCJVSwrmSAw+6B1sEgU7jt6NZBptwGq2k/V0kjyu2GizMFtg==", "dependencies": { - "SkiaSharp.NativeAssets.Win32": "3.119.1", - "SkiaSharp.NativeAssets.macOS": "3.119.1" + "SkiaSharp.NativeAssets.Win32": "3.119.2", + "SkiaSharp.NativeAssets.macOS": "3.119.2" } }, "SkiaSharp.NativeAssets.Linux": { "type": "CentralTransitive", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "9YNoc4SeKvQhrwiqwT4ezkNfMywPdPSK+UFvo/CaoXqLixcnYOTsQKm5BF9mc4+q3vKgDtEgMt0d1ygZhJTEHg==" + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "9WzxSyG/s9Id506j0Ht+Bi5ucOpWKPzd1XXr9TD4fuCafHHy2swRSlbZtC3IDQAsvCH63OerkDJajj43uSv5og==" }, "SkiaSharp.NativeAssets.Win32": { "type": "CentralTransitive", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "8C4GSXVJqSr0y3Tyyv5jz6MJSTVUyYkMjeKrzK+VyZPGLo89MNoUEclVuYahzOCDdtbfXrd2HtxXfDuvoSXrUw==" + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "uYe+da6+GXVgPKkCopzvIZ83DmC8SXXKeUAPrNcztJNsg0SjPQAxfKMOPZqmVjbzznrq/QUIjLUlJSZV/e0IPA==" }, "Svg.Skia": { "type": "CentralTransitive", @@ -322,9 +322,9 @@ }, "System.ServiceModel.Syndication": { "type": "CentralTransitive", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "t28WU9anHmmRD5GHz5qBv/tokqDmU/Ksexz2DI6jAcoSX+p8VQ7rzHCxubDqK6baOtxEyza/oHECzBGi3rHyHQ==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "mloJBxfbjYXgfcfMvH40UWwzekATlUzHLMKPQpg4qab+gpHRgQkhgo4DLz3v7Kacn6000sqoNkxiyk0pJ5x/ig==" }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index b344021b..c06f640c 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -4,9 +4,9 @@ "net10.0": { "Markdig": { "type": "Direct", - "requested": "[0.44.0, )", - "resolved": "0.44.0", - "contentHash": "X+CYMjcUnh/yO24wOSQxVFLiGqWrrtXJ5M7toHiM1Zk4Fg9UMLN5fkaq6FSOWH+mIprsHHgDMlq3MJhmrXalhg==" + "requested": "[0.45.0, )", + "resolved": "0.45.0", + "contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg==" }, "Microsoft.ClearScript": { "type": "Direct", @@ -35,25 +35,25 @@ }, "SkiaSharp": { "type": "Direct", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "+Ru1BTSZQne3Vp+vbSb50Ke3Nlc3ZnItxx4+751J9WZ8YzLKAV/n+9DAo4zFTyeCI//ueT63c+VybmTTpYBEiw==", + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "nmy2dOFWPvQKMglfpjz8+/xQQcSrL9jzul3cUyzCJVSwrmSAw+6B1sEgU7jt6NZBptwGq2k/V0kjyu2GizMFtg==", "dependencies": { - "SkiaSharp.NativeAssets.Win32": "3.119.1", - "SkiaSharp.NativeAssets.macOS": "3.119.1" + "SkiaSharp.NativeAssets.Win32": "3.119.2", + "SkiaSharp.NativeAssets.macOS": "3.119.2" } }, "SkiaSharp.NativeAssets.Linux": { "type": "Direct", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "9YNoc4SeKvQhrwiqwT4ezkNfMywPdPSK+UFvo/CaoXqLixcnYOTsQKm5BF9mc4+q3vKgDtEgMt0d1ygZhJTEHg==" + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "9WzxSyG/s9Id506j0Ht+Bi5ucOpWKPzd1XXr9TD4fuCafHHy2swRSlbZtC3IDQAsvCH63OerkDJajj43uSv5og==" }, "SkiaSharp.NativeAssets.Win32": { "type": "Direct", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "8C4GSXVJqSr0y3Tyyv5jz6MJSTVUyYkMjeKrzK+VyZPGLo89MNoUEclVuYahzOCDdtbfXrd2HtxXfDuvoSXrUw==" + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "uYe+da6+GXVgPKkCopzvIZ83DmC8SXXKeUAPrNcztJNsg0SjPQAxfKMOPZqmVjbzznrq/QUIjLUlJSZV/e0IPA==" }, "Svg.Skia": { "type": "Direct", @@ -68,9 +68,9 @@ }, "System.ServiceModel.Syndication": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "t28WU9anHmmRD5GHz5qBv/tokqDmU/Ksexz2DI6jAcoSX+p8VQ7rzHCxubDqK6baOtxEyza/oHECzBGi3rHyHQ==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "mloJBxfbjYXgfcfMvH40UWwzekATlUzHLMKPQpg4qab+gpHRgQkhgo4DLz3v7Kacn6000sqoNkxiyk0pJ5x/ig==" }, "YamlDotNet": { "type": "Direct", @@ -136,8 +136,8 @@ }, "SkiaSharp.NativeAssets.macOS": { "type": "Transitive", - "resolved": "3.119.1", - "contentHash": "6hR3BdLhApjDxR1bFrJ7/lMydPfI01s3K+3WjIXFUlfC0MFCFCwRzv+JtzIkW9bDXs7XUVQS+6EVf0uzCasnGQ==" + "resolved": "3.119.2", + "contentHash": "I2jMGQ/26KOnc6iAoR+Mxh9vSJJ2vioJyj9aJ9OL5yEZyXothXJxf4vBMqnSaiXMqiiU1scG7KqtT0CLkmMmWA==" }, "Svg.Custom": { "type": "Transitive", From 73ce73ec04353c6094b2cb31496ad4496172fe9a Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Mon, 16 Feb 2026 22:02:25 +0100 Subject: [PATCH 16/41] Added --verify option to links command --- Commands.md | 8 +- Source/BookGen.Contents/BookGenShell.ps1 | 7 +- Source/BookGen/Commands/LinksCommand.cs | 113 ++++++++++++++++-- Source/BookGen/Properties/launchSettings.json | 5 + 4 files changed, 120 insertions(+), 13 deletions(-) diff --git a/Commands.md b/Commands.md index 03855b09..30923a51 100644 --- a/Commands.md +++ b/Commands.md @@ -381,8 +381,8 @@ Arguments: Scans all markdown files in the current book and writes the lins to a markdown file, named links.md ``` -BookGen Links [-v] [-d [directory]] -BookGen Links [--verbose] [--dir [directory]] +BookGen Links [-vf] [-v] [-d [directory]] +BookGen Links [--verify] [--verbose] [--dir [directory]] ``` Arguments: @@ -395,6 +395,10 @@ Arguments: Optional argument, turns on detailed logging. Usefull for locating issues +* `-vf`, `--verify`: + + Optional argument. When specified, the command will verify if the links are accessible and will print the result to the console. If not specified, the command will only write the links to the output file. + # Math2Svg Renders a single markdown file containing Tex formulas to svg files diff --git a/Source/BookGen.Contents/BookGenShell.ps1 b/Source/BookGen.Contents/BookGenShell.ps1 index a845e519..af7ef4a3 100644 --- a/Source/BookGen.Contents/BookGenShell.ps1 +++ b/Source/BookGen.Contents/BookGenShell.ps1 @@ -412,11 +412,14 @@ Register-ArgumentCompleter -Native -CommandName git -ScriptBlock { # set prompt function prompt { $git = $(BookGen.Shellprog.exe "prompt" $(Get-Location).Path) + $location = (Get-Location).Path + $topLine = "╭╴$location"+"`n" + if (-not [string]::IsNullOrWhiteSpace($git)) { - 'PS ' + $(Get-Location) + "`n" + $git + $(if ($NestedPromptLevel -ge 1) { '>>' }) + ' > ' + $topLine + '╰╴ PS ' + $git + $(if ($NestedPromptLevel -ge 1) { '>>' }) + ' > ' } else { - 'PS ' + $(Get-Location) + $(if ($NestedPromptLevel -ge 1) { '>>' }) + ' > ' + $topLine + '╰╴ PS ' + $(if ($NestedPromptLevel -ge 1) { '>>' }) + ' > ' } } diff --git a/Source/BookGen/Commands/LinksCommand.cs b/Source/BookGen/Commands/LinksCommand.cs index f377359c..19b84962 100644 --- a/Source/BookGen/Commands/LinksCommand.cs +++ b/Source/BookGen/Commands/LinksCommand.cs @@ -3,6 +3,8 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using System.Collections.Concurrent; +using System.Net; using System.Text.RegularExpressions; using Bookgen.Lib; @@ -17,8 +19,15 @@ namespace BookGen.Commands; [CommandName("links")] -internal sealed partial class LinksCommand : AsyncCommand +internal sealed partial class LinksCommand : AsyncCommand { + public sealed class LinkArguments : BookGenArgumentBase + { + [Switch("vf", "verify")] + public bool Verify { get; set; } + } + + private readonly IWritableFileSystem _soruce; private readonly ILogger _logger; @@ -28,7 +37,7 @@ public LinksCommand(IWritableFileSystem soruce, ILogger logger) _logger = logger; } - public override async Task ExecuteAsync(BookGenArgumentBase arguments, IReadOnlyList context) + public override async Task ExecuteAsync(LinkArguments arguments, IReadOnlyList context) { _soruce.Scope = arguments.Directory; @@ -42,6 +51,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea } Dictionary allLinks = new(); + Dictionary badLinks = new(); foreach (var chapter in env.TableOfContents.Chapters) { @@ -54,27 +64,49 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea var text = await _soruce.ReadAllTextAsync(file); chapterLinks.UnionWith(GetLinks(text)); } + + if (arguments.Verify) + { + _logger.LogInformation("Verifying links in {chapter}...", chapter.Title); + ConcurrentDictionary linksWithIssues = await VerifyLinks(chapterLinks); + if (linksWithIssues.Count > 0) + { + _logger.LogWarning("Found {count} links with issues in {chapter}:", linksWithIssues.Count, chapter.Title); + badLinks.Add(chapter.Title, linksWithIssues.Select(kvp => $"{kvp.Key} - {kvp.Value}").ToArray()); + } + } + allLinks.Add(chapter.Title, chapterLinks.ToArray()); chapterLinks.Clear(); } + await WriteMarkdown(allLinks, "links.md"); + + if (arguments.Verify) + { + await WriteMarkdown(badLinks, "links.issues.md"); + } + + return ExitCodes.Success; + } + + private async Task WriteMarkdown(Dictionary dataSet, string fileName) + { MarkdownBuilder markdown = new(); - foreach (var linkData in allLinks) + foreach (var linkData in dataSet) { markdown.Heading(2, linkData.Key); markdown.UnorderedList(linkData.Value); } - _logger.LogInformation("Writing links.md..."); - await _soruce.WriteAllTextAsync("links.md", markdown.ToString()); - - return ExitCodes.Success; + _logger.LogInformation("Writing {file}...", fileName); + await _soruce.WriteAllTextAsync(fileName, markdown.ToString()); } [GeneratedRegex(@"https?://[^\s\)\]\}""'<>]+")] - private partial Regex Links { get; } + private static partial Regex Links { get; } - private IEnumerable GetLinks(string text) + private static IEnumerable GetLinks(string text) { var links = Links.Matches(text); foreach (Match link in links) @@ -82,4 +114,67 @@ private IEnumerable GetLinks(string text) yield return link.Value; } } + + private async Task> VerifyLinks(HashSet chapterLinks) + { + ConcurrentDictionary linksWithIssues = new(); + + await Parallel.ForEachAsync(chapterLinks, async (link, cancellationToken) => + { + using var client = CreateHttpClient(); + + try + { + _logger.LogDebug("Verifying {link}...", link); + using var request = new HttpRequestMessage(HttpMethod.Head, link); + + using var response = await client.SendAsync(request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken); + + // If HEAD is not allowed, fall back to a minimal GET + if (response.StatusCode == HttpStatusCode.MethodNotAllowed || + response.StatusCode == HttpStatusCode.NotImplemented) + { + using var getRequest = new HttpRequestMessage(HttpMethod.Get, link); + using var getResponse = await client.SendAsync(getRequest, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken); + + if (!getResponse.IsSuccessStatusCode) + { + linksWithIssues.TryAdd(link, getResponse.StatusCode); + } + + return; + } + + if (!response.IsSuccessStatusCode) + { + linksWithIssues.TryAdd(link, response.StatusCode); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error verifying link: {link}", link); + linksWithIssues.TryAdd(link, HttpStatusCode.ServiceUnavailable); + } + }); + + return linksWithIssues; + } + + private static HttpClient CreateHttpClient() + { + var client = new HttpClient + { + Timeout = TimeSpan.FromSeconds(5), + }; + + client.DefaultRequestHeaders.UserAgent.Add( + new System.Net.Http.Headers.ProductInfoHeaderValue("BookGenLinkChecker", "1.0")); + + return client; + } + } diff --git a/Source/BookGen/Properties/launchSettings.json b/Source/BookGen/Properties/launchSettings.json index 39bbda93..ab444ce0 100644 --- a/Source/BookGen/Properties/launchSettings.json +++ b/Source/BookGen/Properties/launchSettings.json @@ -48,6 +48,11 @@ "Help": { "commandName": "Project", "commandLineArgs": "help" + }, + "Links --verify": { + "commandName": "Project", + "commandLineArgs": "links --verify", + "workingDirectory": "G:\\Konyv\\hellocsharp" } } } \ No newline at end of file From 7642e69de24c9ea355e3f06f2d0d9c26565334f9 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 20 Feb 2026 18:54:04 +0100 Subject: [PATCH 17/41] MD2HTML: Fix image loading --- .../BookGen.Vfs/MultiReadScopeFileSystem.cs | 122 ++++++++++++++++++ Source/BookGen/Commands/Md2HtmlCommand.cs | 10 +- 2 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 Source/BookGen.Vfs/MultiReadScopeFileSystem.cs diff --git a/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs b/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs new file mode 100644 index 00000000..c4a929b4 --- /dev/null +++ b/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2025 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +namespace BookGen.Vfs; + +public sealed class MultiReadScopeFileSystem : IReadOnlyFileSystem +{ + private readonly HashSet _scopes; + + private string Resolve(string path) + { + foreach (var scope in _scopes) + { + var fullPath = Path.GetFullPath(Path.Combine(scope, path)); + if (File.Exists(fullPath)) + return fullPath; + } + throw new InvalidOperationException($"{path} can't be found"); + } + + public MultiReadScopeFileSystem(params IEnumerable scopes) + { + _scopes = new HashSet(scopes); + } + + public string Scope + { + get => string.Join(Environment.NewLine, _scopes); + set => throw new NotSupportedException(); + } + + public bool DirectoryExists(string path) + { + foreach (var scope in _scopes) + { + var fullPath = Path.GetFullPath(Path.Combine(scope, path)); + + if (Directory.Exists(fullPath)) + return true; + } + return false; + } + + public bool FileExists(string path) + { + foreach (var scope in _scopes) + { + var fullPath = Path.GetFullPath(Path.Combine(scope, path)); + + if (File.Exists(fullPath)) + return true; + } + return false; + } + + public IEnumerable GetDirectories(string path, bool recursive) + { + foreach (var scope in _scopes) + { + string fullPath = Path.GetFullPath(Path.Combine(scope, path)); + IEnumerable directories = Directory.EnumerateDirectories(fullPath, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); + foreach (var directory in directories) + { + yield return directory; + } + } + } + + public IEnumerable GetFiles(string path, string filter, bool recursive) + { + foreach (var scope in _scopes) + { + string fullPath = Path.GetFullPath(Path.Combine(scope, path)); + IEnumerable files = Directory.EnumerateFiles(fullPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); + foreach (var file in files) + { + yield return file; + } + } + } + + public long GetFileSize(string path) + { + var actualPath = Resolve(path); + FileInfo fileInfo = new FileInfo(actualPath); + return fileInfo.Length; + } + + public DateTime GetLastModifiedUtc(string path) + { + var actualPath = Resolve(path); + return Directory.Exists(actualPath) + ? Directory.GetLastWriteTimeUtc(actualPath) + : File.GetLastWriteTimeUtc(actualPath); + } + + public Stream OpenReadStream(string path) + { + var actualPath = Resolve(path); + return File.OpenRead(actualPath); + } + + public TextReader OpenTextReader(string path) + { + var actualPath = Resolve(path); + return File.OpenText(actualPath); + } + + public string ReadAllText(string path) + { + var actualPath = Resolve(path); + return File.ReadAllText(actualPath); + } + + public async Task ReadAllTextAsync(string path) + { + var actualPath = Resolve(path); + return await File.ReadAllTextAsync(actualPath); + } +} diff --git a/Source/BookGen/Commands/Md2HtmlCommand.cs b/Source/BookGen/Commands/Md2HtmlCommand.cs index f5e2355f..8dfe138a 100644 --- a/Source/BookGen/Commands/Md2HtmlCommand.cs +++ b/Source/BookGen/Commands/Md2HtmlCommand.cs @@ -105,19 +105,23 @@ public Md2HtmlCommand(ILogger log, IWritableFileSystem fileSystem, IAssetSource public override int Execute(Md2HtmlArguments arguments, IReadOnlyList context) { - (string md, DateTime lastmodified) = _fileSystem.ReadInputFiles(arguments.InputFiles); + IEnumerable inputFolders = arguments.InputFiles.Select(i => Path.GetDirectoryName(i)); + + MultiReadScopeFileSystem inputFilesScope = new MultiReadScopeFileSystem(inputFolders!); + + (string md, DateTime lastmodified) = inputFilesScope.ReadInputFiles(arguments.InputFiles); string? pageTemplate = string.Empty; if (string.IsNullOrEmpty(arguments.Template)) pageTemplate = _assetSource.GetAsset(BundledAssets.TemplateSinglePage); else - pageTemplate = _fileSystem.ReadAllText(arguments.Template); + pageTemplate = inputFilesScope.ReadAllText(arguments.Template); if (!ValidateTemplate(pageTemplate)) return ExitCodes.GeneralError; - var imgService = new ImgService(_fileSystem, _log, new ImageConfig + var imgService = new ImgService(inputFilesScope, _log, new ImageConfig { SvgRecode = arguments.SvgPassthrough ? SvgRecodeOption.Passtrough : SvgRecodeOption.AsWebp, ImageQualityOnResize = 90, From a769cb6b834cae3d7ceaedfdc5c8fced00e9f67e Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 20 Feb 2026 18:54:54 +0100 Subject: [PATCH 18/41] Fix: Base64 encoding of images when in passtrough mode --- Source/Bookgen.Lib/ImageService/Utils.cs | 57 ++++++++++++++++++++---- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/Source/Bookgen.Lib/ImageService/Utils.cs b/Source/Bookgen.Lib/ImageService/Utils.cs index 637e1197..2c0cc361 100644 --- a/Source/Bookgen.Lib/ImageService/Utils.cs +++ b/Source/Bookgen.Lib/ImageService/Utils.cs @@ -3,6 +3,7 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using System.Buffers; using System.Diagnostics; using System.Text; @@ -127,15 +128,55 @@ public static SKData Encode(Stream fileData, int resizeWith, int resizeHeight, i public static string Base64Encode(Stream fileData) { - StringBuilder builder = new StringBuilder(); - byte[] buffer = new byte[4096]; - int read = 0; - do + const int readBufferSize = 4096; + + int size = (int)Math.Ceiling(4 * (fileData.Length / 3.0)); + + var stringBuilder = new StringBuilder(size); + + byte[] remainder = new byte[2]; + int remainderCount = 0; + + byte[] readBuffer = ArrayPool.Shared.Rent(readBufferSize); + int bytesRead; + + try { - read = fileData.Read(buffer, 0, buffer.Length); - builder.Append(Convert.ToBase64String(buffer, 0, read)); + while ((bytesRead = fileData.Read(readBuffer, 0, readBuffer.Length)) > 0) + { + int totalBytesToProcess = remainderCount + bytesRead; + + // Determine how many bytes can form complete 3-byte triplets. + var bytesToTakeForEncoding = (totalBytesToProcess / 3) * 3; + + if (bytesToTakeForEncoding > 0) + { + var chunk = ArrayPool.Shared.Rent(bytesToTakeForEncoding); + remainder.AsSpan(0, remainderCount).CopyTo(chunk); + readBuffer.AsSpan(0, bytesToTakeForEncoding - remainderCount).CopyTo(chunk.AsSpan(remainderCount)); + stringBuilder.Append(Convert.ToBase64String(chunk, 0, bytesToTakeForEncoding)); + ArrayPool.Shared.Return(chunk); + + remainderCount = bytesRead - (bytesToTakeForEncoding - remainderCount); + readBuffer.AsSpan(bytesRead - remainderCount, remainderCount).CopyTo(remainder); + } + else + { + readBuffer.AsSpan(0, bytesRead).CopyTo(remainder.AsSpan(remainderCount)); + remainderCount += bytesRead; + } + } + + if (remainderCount > 0) + { + stringBuilder.Append(Convert.ToBase64String(remainder.AsSpan(0, remainderCount).ToArray())); + } + } + finally + { + ArrayPool.Shared.Return(readBuffer); } - while (read > 0); - return builder.ToString(); + + return stringBuilder.ToString(); } } From be7ae19a3b7c2273f9a74366b2d01732fedaace0 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 20 Feb 2026 19:07:23 +0100 Subject: [PATCH 19/41] Schemas command: Now uses same schema transformer logic as VFS --- .../Internals/JsonSchemaTransformer.cs | 7 +- Source/BookGen/Commands/SchemasCommand.cs | 65 +++---------------- 2 files changed, 12 insertions(+), 60 deletions(-) diff --git a/Source/BookGen.Vfs/Internals/JsonSchemaTransformer.cs b/Source/BookGen.Vfs/Internals/JsonSchemaTransformer.cs index 0239b769..8262b81f 100644 --- a/Source/BookGen.Vfs/Internals/JsonSchemaTransformer.cs +++ b/Source/BookGen.Vfs/Internals/JsonSchemaTransformer.cs @@ -41,6 +41,7 @@ public static JsonNode TransformSchemaNode(JsonSchemaExporterContext context, Js requiredArray.Add(property.Key); } obj["required"] = requiredArray; + obj["additionalProperties"] = false; } AddDecription(node, GetAttribute(attributeProvider)); @@ -106,7 +107,7 @@ static bool IsNumeric(Type? propertyType) }; } - if (rangeAttribute == null || node is not JsonObject jObj) + if (rangeAttribute is null || node is not JsonObject jObj) return; if (IsNumeric(propertyInfo?.PropertyType)) @@ -118,7 +119,7 @@ static bool IsNumeric(Type? propertyType) private static void AddRegex(JsonNode node, string? regexPattern, JsonPropertyInfo? propertyInfo) { - if (regexPattern == null || node is not JsonObject jObj) + if (regexPattern is null || node is not JsonObject jObj) return; if (propertyInfo?.PropertyType == typeof(string)) @@ -135,7 +136,7 @@ static bool IsCollection(Type? propertyType) || propertyType?.IsAssignableTo(typeof(System.Collections.IEnumerable)) == true; } - if (length == null || node is not JsonObject jObj) + if (length is null || node is not JsonObject jObj) return; if (propertyInfo?.PropertyType == typeof(string)) diff --git a/Source/BookGen/Commands/SchemasCommand.cs b/Source/BookGen/Commands/SchemasCommand.cs index 4733ee61..ae8491c0 100644 --- a/Source/BookGen/Commands/SchemasCommand.cs +++ b/Source/BookGen/Commands/SchemasCommand.cs @@ -1,15 +1,10 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- -using System.ComponentModel; -using System.Diagnostics; -using System.Reflection; using System.Text.Json; -using System.Text.Json.Nodes; using System.Text.Json.Schema; -using System.Text.Json.Serialization; using Bookgen.Lib; using Bookgen.Lib.Domain.IO; @@ -28,73 +23,29 @@ internal sealed class SchemasCommand : Command { private readonly IWritableFileSystem _writableFileSystem; private readonly ILogger _logger; - private readonly JsonSerializerOptions _options; - private readonly JsonSchemaExporterOptions _exporterOptions; - + public SchemasCommand(IWritableFileSystem writableFileSystem, ILogger logger) { _writableFileSystem = writableFileSystem; _logger = logger; - _options = new JsonSerializerOptions(JsonSerializerOptions.Default) - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonStringEnumConverter() - }, - }; - _exporterOptions = new() - { - TransformSchemaNode = (context, schema) => - { - // Determine if a type or property and extract the relevant attribute provider. - ICustomAttributeProvider? attributeProvider = context.PropertyInfo is not null - ? context.PropertyInfo.AttributeProvider - : context.TypeInfo.Type; - - // Look up any description attributes. - DescriptionAttribute? descriptionAttr = attributeProvider? - .GetCustomAttributes(inherit: true) - .Select(attr => attr as DescriptionAttribute) - .FirstOrDefault(attr => attr is not null); - - // Apply description attribute to the generated schema. - if (descriptionAttr != null) - { - if (schema is not JsonObject jObj) - { - // Handle the case where the schema is a Boolean. - JsonValueKind valueKind = schema.GetValueKind(); - Debug.Assert(valueKind is JsonValueKind.True or JsonValueKind.False); - schema = jObj = new JsonObject(); - if (valueKind is JsonValueKind.False) - { - jObj.Add("not", true); - } - } - - jObj.Insert(0, "description", descriptionAttr.Description); - } - - return schema; - } - }; } public override int Execute(BookGenArgumentBase arguments, IReadOnlyList context) { + JsonSerializerOptions options = JsonOptions.SerializerOptions; + JsonSchemaExporterOptions exporterOptions = JsonOptions.ExporterOptions; + MarkdownBuilder markdownBuilder = new(); markdownBuilder.Heading(1, "Bookgen Schemas") .Paragraph("This document contains the schemas used by Bookgen.") .Heading(2, "Bookgen.json") - .CodeBlock(_options.GetJsonSchemaAsNode(typeof(Config), _exporterOptions).ToString(), "json") + .CodeBlock(options.GetJsonSchemaAsNode(typeof(Config), exporterOptions).ToString(), "json") .Heading(2, "Table of contents file") - .CodeBlock(_options.GetJsonSchemaAsNode(typeof(TableOfContents), _exporterOptions).ToString(), "json") + .CodeBlock(options.GetJsonSchemaAsNode(typeof(TableOfContents), exporterOptions).ToString(), "json") .Heading(3, "Page frontmatter") .Paragraph("Each page in the table of contents must have a YAML front matter.") - .CodeBlock(_options.GetJsonSchemaAsNode(typeof(FrontMatter), _exporterOptions).ToString(), "json"); + .CodeBlock(options.GetJsonSchemaAsNode(typeof(FrontMatter), exporterOptions).ToString(), "json"); _logger.LogInformation("Writing schemas.md..."); _writableFileSystem.Scope = arguments.Directory; From 7bb4d543bebf56a3b2e5eba3555280c0e22ec5c8 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 20 Feb 2026 19:25:16 +0100 Subject: [PATCH 20/41] Fixes tests for md2html command --- Source/BookGen.Vfs/FileSystemFactory.cs | 28 +++++++++++++++++++ Source/BookGen.Vfs/IFileSystemFactory.cs | 13 +++++++++ .../BookGen.Vfs/MultiReadScopeFileSystem.cs | 2 +- Source/BookGen/Commands/Md2HtmlCommand.cs | 8 ++++-- Source/BookGen/Program.cs | 1 + .../Bookgen.Tests/Commands/CommandTestBase.cs | 1 + .../Commands/UT_Md2HtmlCommand.cs | 17 ++++++----- 7 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 Source/BookGen.Vfs/FileSystemFactory.cs create mode 100644 Source/BookGen.Vfs/IFileSystemFactory.cs diff --git a/Source/BookGen.Vfs/FileSystemFactory.cs b/Source/BookGen.Vfs/FileSystemFactory.cs new file mode 100644 index 00000000..89610458 --- /dev/null +++ b/Source/BookGen.Vfs/FileSystemFactory.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2026 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +namespace BookGen.Vfs; + +public sealed class FileSystemFactory : IFileSystemFactory +{ + public IReadOnlyFileSystem CreateMultiReadScopeFileSystem(params IEnumerable scopes) + => new MultiReadScopeFileSystem(scopes); + + public IReadOnlyFileSystem CreateReadOnlyFileSystem(string scope = "") + { + return new ReadOnlyFileSystem + { + Scope = scope + }; + } + + public IWritableFileSystem CreateWritableFileSystem(string scope = "") + { + return new FileSystem + { + Scope = scope + }; + } +} diff --git a/Source/BookGen.Vfs/IFileSystemFactory.cs b/Source/BookGen.Vfs/IFileSystemFactory.cs new file mode 100644 index 00000000..66868c00 --- /dev/null +++ b/Source/BookGen.Vfs/IFileSystemFactory.cs @@ -0,0 +1,13 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2026 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +namespace BookGen.Vfs; + +public interface IFileSystemFactory +{ + IReadOnlyFileSystem CreateMultiReadScopeFileSystem(params IEnumerable scopes); + IWritableFileSystem CreateWritableFileSystem(string scope = ""); + IReadOnlyFileSystem CreateReadOnlyFileSystem(string scope = ""); +} diff --git a/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs b/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs index c4a929b4..29af0b89 100644 --- a/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs +++ b/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- diff --git a/Source/BookGen/Commands/Md2HtmlCommand.cs b/Source/BookGen/Commands/Md2HtmlCommand.cs index 8dfe138a..609c9b7e 100644 --- a/Source/BookGen/Commands/Md2HtmlCommand.cs +++ b/Source/BookGen/Commands/Md2HtmlCommand.cs @@ -88,6 +88,7 @@ public override ValidationResult Validate(IValidationContext context) } private readonly ILogger _log; + private readonly IFileSystemFactory _fileSystemFactory; private readonly IWritableFileSystem _fileSystem; private readonly IAssetSource _assetSource; private const string TitleTag = "{{Title}}"; @@ -95,10 +96,11 @@ public override ValidationResult Validate(IValidationContext context) private readonly TemplateEngine _templateEngine; - public Md2HtmlCommand(ILogger log, IWritableFileSystem fileSystem, IAssetSource assetSource) + public Md2HtmlCommand(ILogger log, IFileSystemFactory fileSystemFactory, IAssetSource assetSource) { _log = log; - _fileSystem = fileSystem; + _fileSystemFactory = fileSystemFactory; + _fileSystem = fileSystemFactory.CreateWritableFileSystem(); _assetSource = assetSource; _templateEngine = new TemplateEngine(log, assetSource); } @@ -107,7 +109,7 @@ public override int Execute(Md2HtmlArguments arguments, IReadOnlyList co { IEnumerable inputFolders = arguments.InputFiles.Select(i => Path.GetDirectoryName(i)); - MultiReadScopeFileSystem inputFilesScope = new MultiReadScopeFileSystem(inputFolders!); + IReadOnlyFileSystem inputFilesScope = _fileSystemFactory.CreateMultiReadScopeFileSystem(inputFolders!); (string md, DateTime lastmodified) = inputFilesScope.ReadInputFiles(arguments.InputFiles); diff --git a/Source/BookGen/Program.cs b/Source/BookGen/Program.cs index f274c009..98408469 100644 --- a/Source/BookGen/Program.cs +++ b/Source/BookGen/Program.cs @@ -48,6 +48,7 @@ ioc.AddSingleton(info); ioc.AddSingleton(runnerProxy); ioc.AddSingleton(ZipAssetSoruce.DefaultAssets()); +ioc.AddSingleton(); ioc.AddSingleton(new HelpProvider(logger, runnerProxy)); ioc.AddTransient(); ioc.AddTransient(); diff --git a/Test/Bookgen.Tests/Commands/CommandTestBase.cs b/Test/Bookgen.Tests/Commands/CommandTestBase.cs index 42ab4a12..6ea47afe 100644 --- a/Test/Bookgen.Tests/Commands/CommandTestBase.cs +++ b/Test/Bookgen.Tests/Commands/CommandTestBase.cs @@ -19,6 +19,7 @@ internal abstract class CommandTestBase where TCommand : ICommand protected readonly Mock LoggerMock = new Mock(MockBehavior.Strict); protected readonly Mock AssetSourceMock = new Mock(MockBehavior.Strict); protected readonly Mock CommandRunnerProxyMock = new Mock(MockBehavior.Strict); + protected readonly Mock FilesystemFactoryMock = new Mock(MockBehavior.Strict); protected ICommand Command { get; private set; } diff --git a/Test/Bookgen.Tests/Commands/UT_Md2HtmlCommand.cs b/Test/Bookgen.Tests/Commands/UT_Md2HtmlCommand.cs index 0601778d..af4a1704 100644 --- a/Test/Bookgen.Tests/Commands/UT_Md2HtmlCommand.cs +++ b/Test/Bookgen.Tests/Commands/UT_Md2HtmlCommand.cs @@ -15,19 +15,22 @@ namespace Bookgen.Tests.Commands; [TestFixture] internal class UT_Md2HtmlCommand : CommandTestBase { - protected override Md2HtmlCommand CreateSut() - => new Md2HtmlCommand(LoggerMock.Object, FileSystemMock.Object, AssetSourceMock.Object); + private readonly Mock MultiReadScopeMock = new Mock(MockBehavior.Strict); protected override void SetupMocks() { + MultiReadScopeMock.Setup(fs => fs.ReadAllText("test.md")).Returns("test"); + MultiReadScopeMock.Setup(fs => fs.GetLastModifiedUtc("test.md")).Returns(new DateTime(2024, 1, 1)); AssetSourceMock.Setup(a => a.GetAsset(BundledAssets.TemplateSinglePage)).Returns("

{{Title}}

{{Content}}"); AssetSourceMock.Setup(a => a.GetAsset(BundledAssets.PrismJs)).Returns(""); - FileSystemMock.As().Setup(fs => fs.ReadAllText("test.md")).Returns("test"); - FileSystemMock.As().Setup(fs => fs.GetLastModifiedUtc("test.md")).Returns(new DateTime(2024, 1, 1)); FileSystemMock.Setup(fs => fs.WriteAllText("out.html", It.IsAny())); - + FilesystemFactoryMock.Setup(f => f.CreateMultiReadScopeFileSystem(It.IsAny>())).Returns(MultiReadScopeMock.Object); + FilesystemFactoryMock.Setup(f => f.CreateWritableFileSystem(It.IsAny())).Returns(FileSystemMock.Object); } + protected override Md2HtmlCommand CreateSut() + => new Md2HtmlCommand(LoggerMock.Object, FilesystemFactoryMock.Object, AssetSourceMock.Object); + [Test] public async Task EnsureThat_GenerateRawWorks() { @@ -48,7 +51,7 @@ public async Task EnsureThat_GenerateRawWorks() using (Assert.EnterMultipleScope()) { Assert.That(exitCode, Is.EqualTo(0)); - FileSystemMock.Verify(fs => fs.ReadAllText("test.md"), Times.AtLeastOnce()); + MultiReadScopeMock.Verify(fs => fs.ReadAllText("test.md"), Times.AtLeastOnce()); FileSystemMock.Verify(fs => fs.WriteAllText("out.html", expectedContent), Times.Once); } } @@ -72,7 +75,7 @@ public async Task EnsureThat_GenerateHtml_Works() { Assert.That(exitCode, Is.EqualTo(0)); AssetSourceMock.Verify(a => a.GetAsset(BundledAssets.TemplateSinglePage), Times.Once); - FileSystemMock.Verify(fs => fs.ReadAllText("test.md"), Times.Once); + MultiReadScopeMock.Verify(fs => fs.ReadAllText("test.md"), Times.Once); FileSystemMock.Verify(fs => fs.WriteAllText("out.html", It.IsAny()), Times.Once); } } From 02f7048cd43ad128418c7bc866fac4fd95a9439a Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 20 Feb 2026 22:07:52 +0100 Subject: [PATCH 21/41] Explicit var styling --- .../ArgumentParsing/ArgumentParser.cs | 16 +++---- .../ArgumentParsing/Autocomplete.cs | 6 +-- Source/BookGen.Cli/BookGen.Cli.csproj | 1 + Source/BookGen.Cli/CommandRunner.cs | 47 +++++++++++-------- .../BookGen.Contents/BookGen.Contents.csproj | 1 + .../BookGen.Shell.Shared.csproj | 1 + .../GitCommandProvider.cs | 4 +- Source/BookGen.Shell.Shared/ProcessRunner.cs | 2 +- .../ShellAutoCompleteFilter.cs | 6 +-- .../BookGen.Shellprog.csproj | 1 + .../CommandCode/Cdg/CdgSelector.cs | 10 ++-- .../CommandCode/Cdg/SelectionItemFactory.cs | 6 +-- .../CommandCode/Organize/RuleEngine.cs | 2 +- .../CommandCode/Organize/RuleLoader.cs | 2 +- .../GitAutoCompleteCommand.cs | 2 +- Source/BookGen.Shellprog/GitCommandBase.cs | 10 ++-- Source/BookGen.Shellprog/OrganizeCommand.cs | 2 +- Source/BookGen.Shellprog/Program.cs | 4 +- Source/BookGen.Shellprog/PromptCommand.cs | 5 +- Source/BookGen.Vfs/ApiClient.cs | 4 +- Source/BookGen.Vfs/BookGen.Vfs.csproj | 1 + Source/BookGen.Vfs/Extensions.cs | 6 +-- Source/BookGen.Vfs/FileSystem.cs | 6 +-- .../Internals/JsonSchemaTransformer.cs | 2 +- Source/BookGen.Vfs/ZipBuilder.cs | 16 +++---- Source/BookGen/BookGen.csproj | 1 + Source/BookGen/BuildArguments.cs | 2 +- .../BookGen/Commands/AddFrontMatterCommand.cs | 6 ++- Source/BookGen/Commands/AssemblyDocument.cs | 2 +- Source/BookGen/Commands/ConfigCommand.cs | 6 +-- Source/BookGen/Commands/GuiCommand.cs | 6 +-- Source/BookGen/Commands/ImgConvert.cs | 6 +-- Source/BookGen/Commands/InstallCommand.cs | 6 +-- Source/BookGen/Commands/LinksCommand.cs | 13 ++--- Source/BookGen/Commands/NewPageCommand.cs | 4 +- Source/BookGen/Commands/ShellCommand.cs | 2 +- Source/BookGen/Commands/StatsCommand.cs | 2 +- Source/BookGen/Commands/SubCommandsCommand.cs | 2 +- Source/BookGen/Commands/TemplatesCommand.cs | 2 +- .../Commands/TerminalInstallCommand.cs | 2 +- Source/BookGen/Commands/ToolsCommand.cs | 4 +- Source/BookGen/Extensions.cs | 2 +- Source/BookGen/Infrastructure/HelpProvider.cs | 2 +- Source/BookGen/Infrastructure/HelpRenderer.cs | 3 +- .../Loging/ConsoleLogProvider.cs | 2 +- .../Infrastructure/Terminal/Terminal.cs | 12 ++--- Source/BookGen/Infrastructure/Tools/Digest.cs | 31 ++++++------ .../BookGen/Infrastructure/Tools/Extractor.cs | 10 ++-- Source/BookGen/Program.cs | 4 +- .../Tooldownloaders/TooldownloaderBase.cs | 6 +-- .../AppSettings/AppSettingsBase.cs | 2 +- Source/Bookgen.Lib/BookEnvironment.cs | 9 ++-- Source/Bookgen.Lib/BookStatFactory.cs | 9 ++-- Source/Bookgen.Lib/Bookgen.Lib.csproj | 1 + .../Confighandling/ConfigUpgrader.cs | 10 ++-- .../Bookgen.Lib/Confighandling/JsonMerger.cs | 10 ++-- .../Confighandling/JsonObjectExtensions.cs | 4 +- .../LegacyMigration/LoadLegacyConfig.cs | 4 +- .../LegacyMigration/LoadLegacyTags.cs | 2 +- .../LegacyMigration/LoadLegacyToc.cs | 2 +- .../LegacyMigration/MigrateFiles.cs | 7 ++- .../LegacyMigration/Migrator.cs | 2 +- .../UpgradeSteps/FromVersion2003To2004.cs | 2 +- .../UpgradeSteps/FromVersion2004To2005.cs | 2 +- .../UpgradeSteps/FromVersion2005To2006.cs | 2 +- .../Bookgen.Lib/Domain/IO/TableOfContents.cs | 2 +- .../Domain/Validation/FileExistsAttribute.cs | 2 +- .../WhenNotEmptyFileMustExistAttribute.cs | 2 +- Source/Bookgen.Lib/Http/HttpServer.cs | 8 ++-- Source/Bookgen.Lib/Http/PageFactory.cs | 2 +- Source/Bookgen.Lib/Http/ServerFactory.cs | 2 +- .../ImageService/CachedImageService.cs | 4 +- .../ImageService/ImageConverter.cs | 4 +- Source/Bookgen.Lib/ImageService/ImgService.cs | 2 +- Source/Bookgen.Lib/ImageService/Utils.cs | 2 +- Source/Bookgen.Lib/Internals/Extensions.cs | 6 +-- .../Internals/SerializedObjectValidator.cs | 7 +-- .../Bookgen.Lib/Markdown/BookGenExtension.cs | 2 +- .../Bookgen.Lib/Markdown/MarkdownConverter.cs | 7 +-- Source/Bookgen.Lib/Markdown/RenderSettings.cs | 2 +- .../Renderers/ExtendedLinkInlineRenderer.cs | 3 +- .../Markdown/Renderers/SyntaxRenderer.cs | 5 +- .../Terminal/EmphasisInlineRenderer.cs | 4 +- .../TableOfContents/CustomAutoIdExtension.cs | 22 +++++---- .../Markdown/TableOfContents/HeadingInfos.cs | 2 +- .../TableOfContents/HtmlTocRenderer.cs | 2 +- .../Markdown/TableOfContents/LevelList.cs | 8 ++-- .../TableOfContents/TocBlockParser.cs | 4 +- .../Markdown/TableOfContents/TocExtension.cs | 2 +- .../Pipeline/Epub/CreateContentOpf.cs | 2 +- .../Pipeline/Epub/CreateHtmlPages.cs | 5 +- .../Pipeline/Epub/CreateImageFiles.cs | 2 +- Source/Bookgen.Lib/Pipeline/Epub/CreateNav.cs | 6 +-- .../Bookgen.Lib/Pipeline/Feed/CreateItems.cs | 3 +- .../Bookgen.Lib/Pipeline/Feed/WriteFeeds.cs | 5 +- Source/Bookgen.Lib/Pipeline/Pipeline.cs | 4 +- .../PostProcess/RenderPagesForPostProcess.cs | 3 +- .../Bookgen.Lib/Pipeline/Print/RenderPages.cs | 3 +- .../Bookgen.Lib/Pipeline/Print/WriteHtml.cs | 2 +- .../Bookgen.Lib/Pipeline/Print/WriteXHtml.cs | 2 +- .../CreateEmptyIndexPagesForFolders.cs | 2 +- .../StaticWebsite/RenderTableOfContents.cs | 2 +- .../Pipeline/Wordpress/CreateWpPages.cs | 6 ++- .../Pipeline/Wordpress/WriteExportFile.cs | 2 +- Source/Bookgen.Lib/Templates/Functions.cs | 2 +- .../Bookgen.Lib/Templates/TemplateEngine.cs | 6 +-- Source/Bookgen.Lib/Templates/ViewData.cs | 6 +-- Test/Bookgen.Tests/EmbeddedTestFolder.cs | 6 +-- Test/Bookgen.Tests/Lib/UT_ImgService.cs | 10 ++-- Test/Bookgen.Tests/Lib/UT_JsonMerger.cs | 2 +- Test/Bookgen.Tests/Lib/UT_LevelList.cs | 2 +- .../Shell.Shared/UT_GitParser.cs | 2 +- .../UT_ShellAutoCompleteFilter.cs | 6 +-- 113 files changed, 305 insertions(+), 261 deletions(-) diff --git a/Source/BookGen.Cli/ArgumentParsing/ArgumentParser.cs b/Source/BookGen.Cli/ArgumentParsing/ArgumentParser.cs index ba07ef6c..f12746b1 100644 --- a/Source/BookGen.Cli/ArgumentParsing/ArgumentParser.cs +++ b/Source/BookGen.Cli/ArgumentParsing/ArgumentParser.cs @@ -30,10 +30,10 @@ public ArgumentParser(Type argumentType, ILogger log) private void OrganizeProperties(PropertyInfo[] propertyInfos) { - foreach (var propertyInfo in propertyInfos) + foreach (PropertyInfo propertyInfo in propertyInfos) { - var switchAttribute = propertyInfo.GetCustomAttribute(); - var argumentAttribute = propertyInfo.GetCustomAttribute(); + SwitchAttribute? switchAttribute = propertyInfo.GetCustomAttribute(); + ArgumentAttribute? argumentAttribute = propertyInfo.GetCustomAttribute(); if (switchAttribute != null && argumentAttribute != null) { @@ -70,9 +70,9 @@ public ArgumentsBase Fill(IReadOnlyList args) private void HandleSwitchProperties(object argumentsClass, ArgumentBag argBag) { - foreach (var property in _switchPropertyInfos) + foreach (PropertyInfo property in _switchPropertyInfos) { - var switchAttribute = property.GetCustomAttribute() + SwitchAttribute switchAttribute = property.GetCustomAttribute() ?? throw new System.Diagnostics.UnreachableException(); if (switchAttribute.ShortName.StartsWith('-')) @@ -88,7 +88,7 @@ private void HandleSwitchProperties(object argumentsClass, ArgumentBag argBag) else if (property.PropertyType.IsArray) { string[] values = argBag.GetSwitchValues(switchAttribute); - var elementType = property.PropertyType.GetElementType(); + Type? elementType = property.PropertyType.GetElementType(); if (elementType != null) { var finalValues = values @@ -114,9 +114,9 @@ private void HandleSwitchProperties(object argumentsClass, ArgumentBag argBag) private void HandleArgumentProperties(object argumentsClass, ArgumentBag argBag) { - foreach (var property in _argumentPropertyInfos) + foreach (PropertyInfo property in _argumentPropertyInfos) { - var argumentAttribute = property.GetCustomAttribute() + ArgumentAttribute argumentAttribute = property.GetCustomAttribute() ?? throw new System.Diagnostics.UnreachableException(); if (property.PropertyType.IsArray) diff --git a/Source/BookGen.Cli/ArgumentParsing/Autocomplete.cs b/Source/BookGen.Cli/ArgumentParsing/Autocomplete.cs index 463b4533..4a6ed1d5 100644 --- a/Source/BookGen.Cli/ArgumentParsing/Autocomplete.cs +++ b/Source/BookGen.Cli/ArgumentParsing/Autocomplete.cs @@ -13,11 +13,11 @@ internal static class Autocomplete { public static IEnumerable GetInfo(Type t) { - var properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); + PropertyInfo[] properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); - foreach (var property in properties) + foreach (PropertyInfo property in properties) { - var sw = property.GetCustomAttribute(); + SwitchAttribute? sw = property.GetCustomAttribute(); if (sw != null) { yield return $"-{sw.ShortName}"; diff --git a/Source/BookGen.Cli/BookGen.Cli.csproj b/Source/BookGen.Cli/BookGen.Cli.csproj index 1633e045..8e569b4b 100644 --- a/Source/BookGen.Cli/BookGen.Cli.csproj +++ b/Source/BookGen.Cli/BookGen.Cli.csproj @@ -6,6 +6,7 @@ ..\..\bin\$(Configuration)\ false true + True diff --git a/Source/BookGen.Cli/CommandRunner.cs b/Source/BookGen.Cli/CommandRunner.cs index 22ba41f5..8484600a 100644 --- a/Source/BookGen.Cli/CommandRunner.cs +++ b/Source/BookGen.Cli/CommandRunner.cs @@ -28,7 +28,7 @@ public sealed class CommandRunner private static string GetCommandName(Type t) { - var nameAttribure = t.GetCustomAttribute(); + CommandNameAttribute? nameAttribure = t.GetCustomAttribute(); return nameAttribure?.Name ?? throw new InvalidOperationException($"Command {t.FullName} is missing a {nameof(CommandNameAttribute)}"); } @@ -76,13 +76,13 @@ private static SupportedOs GetCurrentOs() cmd = cmd.BaseType; } - var method = originalType.GetMethod(nameof(AsyncCommand.ExecuteAsync)) + MethodInfo? method = originalType.GetMethod(nameof(AsyncCommand.ExecuteAsync)) ?? originalType.GetMethod(nameof(Command.Execute)); if (method == null) throw new InvalidOperationException($"Command {originalType.FullName} is missing Exetutable method"); - var parameter = method + Type? parameter = method ?.GetParameters() .FirstOrDefault(p => p.ParameterType.IsAssignableTo(typeof(ArgumentsBase))) ?.ParameterType; @@ -101,13 +101,13 @@ private void DefaultExceptionHandler(Exception obj) private ICommand CreateCommand(string commandName) { - var constructor = _commands[commandName] + ConstructorInfo constructor = _commands[commandName] .GetConstructors(BindingFlags.Public | BindingFlags.Instance) .OrderByDescending(c => c.GetParameters().Length) .First(); List contructorParameters = new(); - foreach (var param in constructor.GetParameters()) + foreach (ParameterInfo param in constructor.GetParameters()) { contructorParameters.Add(_serviceProvider.GetRequiredService(param.ParameterType)); } @@ -171,14 +171,14 @@ public CommandRunner AddDefaultCommand() where TCommand : ICommand public CommandRunner AddCommandsFrom(Assembly assembly) { - var commands = assembly + IEnumerable commands = assembly .GetTypes() .Where(t => t.IsAssignableTo(typeof(ICommand))) .Where(t => !t.IsAbstract && !t.IsInterface); - foreach (var command in commands) + foreach (Type? command in commands) { - var name = GetCommandName(command); + string name = GetCommandName(command); if (!_commands.ContainsKey(name)) { _commands.Add(name.ToLower(), command); @@ -195,9 +195,9 @@ public string[] GetAutoCompleteItems(string commandName) { if (_commands.TryGetValue(commandName, out Type? value)) { - var type = value; + Type type = value; - var args = GetArgumentType(type); + Type? args = GetArgumentType(type); if (args != null) { @@ -241,7 +241,7 @@ public async Task Run(IReadOnlyList args) private async Task LoadFromJsonFile(string jsonFile) { - await using var stream = File.OpenRead(jsonFile); + await using FileStream stream = File.OpenRead(jsonFile); return await JsonSerializer.DeserializeAsync(stream, _serializerOptions) ?? throw new InvalidOperationException("Failed to load arguments from json"); } @@ -254,7 +254,7 @@ public async Task RunCommand(string commandName, IReadOnlyList args return _settings.UnknownCommandCodeAndMessage.code; } - var argumentType = GetArgumentType(value); + Type? argumentType = GetArgumentType(value); ICommand command = CreateCommand(commandName); if (!command.SupportedOs.HasFlag(_currentOs)) @@ -274,24 +274,28 @@ public async Task RunCommand(string commandName, IReadOnlyList args && File.Exists(argsJson)) { _log.LogInformation("Loading arguments from {filename}...", jsonFileName); - var items = await LoadFromJsonFile(argsJson); + ArgumentJsonItem[] items = await LoadFromJsonFile(argsJson); - return await ExecuteMultiple(items, argumentType, command); + return await ExecuteMultiple(items, argumentType, command, commandName); } - return await ExecuteSingle(argsToParse, argumentType, command); + return await ExecuteSingle(argsToParse, argumentType, command, commandName); } - private async Task ExecuteSingle(IReadOnlyList argsToParse, Type argumentType, ICommand command) + private async Task ExecuteSingle(IReadOnlyList argsToParse, + Type argumentType, + ICommand command, + string commandName) { ArgumentsBase args = ArgumentsBase.Empty; ArgumentParser parser = new(argumentType, _log); args = parser.Fill(argsToParse); - var validationResult = args.Validate(ValidationContext); + ValidationResult validationResult = args.Validate(ValidationContext); if (!validationResult.IsOk) { _log.LogCritical(validationResult.ToString()); + _log.LogInformation("Use help {commandName} to get help on command", commandName); return _settings.BadParametersExitCode; } @@ -303,12 +307,15 @@ private async Task ExecuteSingle(IReadOnlyList argsToParse, Type ar return await command.ExecuteAsync(args, argsToParse); } - private async Task ExecuteMultiple(ArgumentJsonItem[] items, Type argumentType, ICommand command) + private async Task ExecuteMultiple(ArgumentJsonItem[] items, + Type argumentType, + ICommand command, + string commandName) { - foreach (var item in items) + foreach (ArgumentJsonItem item in items) { _log.LogInformation("Executing {name} from json file...", item.Name); - int exitcode = await ExecuteSingle(item.Arguments, argumentType, command); + int exitcode = await ExecuteSingle(item.Arguments, argumentType, command, commandName); if (exitcode != 0) { _log.LogCritical("Failed to execute {name}. Exit code: {exitcode}", item.Name, exitcode); diff --git a/Source/BookGen.Contents/BookGen.Contents.csproj b/Source/BookGen.Contents/BookGen.Contents.csproj index 9227d78c..87d56789 100644 --- a/Source/BookGen.Contents/BookGen.Contents.csproj +++ b/Source/BookGen.Contents/BookGen.Contents.csproj @@ -5,6 +5,7 @@ enable ..\..\bin\$(Configuration)\ false + True diff --git a/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj b/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj index c07c7470..96febe16 100644 --- a/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj +++ b/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj @@ -5,6 +5,7 @@ enable enable true + True diff --git a/Source/BookGen.Shell.Shared/GitCommandProvider.cs b/Source/BookGen.Shell.Shared/GitCommandProvider.cs index 32380404..0fe6e658 100644 --- a/Source/BookGen.Shell.Shared/GitCommandProvider.cs +++ b/Source/BookGen.Shell.Shared/GitCommandProvider.cs @@ -14,7 +14,7 @@ public static class GitCommandProvider { private static IEnumerable GetBranches(string folder) { - var (exitcode, output, _) = ProcessRunner.RunProcess("git", ["branch", "-a"], 10, folder); + (int exitcode, string? output, string _) = ProcessRunner.RunProcess("git", ["branch", "-a"], 10, folder); if (exitcode != 0) return Enumerable.Empty(); @@ -24,7 +24,7 @@ private static IEnumerable GetBranches(string folder) public static IEnumerable GetGitCommands(string folder) { - var branches = GetBranches(folder); + IEnumerable branches = GetBranches(folder); yield return "git add ."; yield return "git add"; diff --git a/Source/BookGen.Shell.Shared/ProcessRunner.cs b/Source/BookGen.Shell.Shared/ProcessRunner.cs index 3a801b5c..8e724138 100644 --- a/Source/BookGen.Shell.Shared/ProcessRunner.cs +++ b/Source/BookGen.Shell.Shared/ProcessRunner.cs @@ -68,7 +68,7 @@ public static void RunCmdScript(string shellScript, ILogger log) public static void RunPowershellScript(string shellScript, ILogger log) { - var installResult = InstallDetector.GetInstallResult(); + InstallResult installResult = InstallDetector.GetInstallResult(); if (installResult.IsPsCoreInstalled) RunShell(InstallDetector.PowershellCoreExe, $"-ExecutionPolicy Bypass -File \"{shellScript}\"", log); else diff --git a/Source/BookGen.Shell.Shared/ShellAutoCompleteFilter.cs b/Source/BookGen.Shell.Shared/ShellAutoCompleteFilter.cs index dfe484db..3a228434 100644 --- a/Source/BookGen.Shell.Shared/ShellAutoCompleteFilter.cs +++ b/Source/BookGen.Shell.Shared/ShellAutoCompleteFilter.cs @@ -20,12 +20,12 @@ public static IEnumerable DoFilter(IReadOnlyList candidates, str string prefix = input[..cursorposition]; int prefixLength = cursorposition >= prefix.Length ? prefix.Length - 1 : cursorposition; - var filteredCommands = candidates + IEnumerable filteredCommands = candidates .Where(cmd => cmd.StartsWith(prefix)); foreach (var filtered in filteredCommands) { - var (start, _) = GetWordPositions(filtered).FirstOrDefault(p => cursorposition >= p.start && cursorposition <= p.end); + (int start, int _) = GetWordPositions(filtered).FirstOrDefault(p => cursorposition >= p.start && cursorposition <= p.end); yield return filtered[start..]; } } @@ -38,7 +38,7 @@ public static IEnumerable DoFilter(IReadOnlyList candidates, str { if (char.IsWhiteSpace(c)) { - var item = (start, pos); + (int start, int pos) item = (start, pos); start = pos + 1; yield return item; } diff --git a/Source/BookGen.Shellprog/BookGen.Shellprog.csproj b/Source/BookGen.Shellprog/BookGen.Shellprog.csproj index 9e0bc280..fdd92f4b 100644 --- a/Source/BookGen.Shellprog/BookGen.Shellprog.csproj +++ b/Source/BookGen.Shellprog/BookGen.Shellprog.csproj @@ -9,6 +9,7 @@ false ..\..\Branding\icon-shell.ico true + True diff --git a/Source/BookGen.Shellprog/CommandCode/Cdg/CdgSelector.cs b/Source/BookGen.Shellprog/CommandCode/Cdg/CdgSelector.cs index 2f98d322..bfa67f6a 100644 --- a/Source/BookGen.Shellprog/CommandCode/Cdg/CdgSelector.cs +++ b/Source/BookGen.Shellprog/CommandCode/Cdg/CdgSelector.cs @@ -117,7 +117,7 @@ public CdgSelector(string startDirectory, bool showHidden) Icon = ":eye: ", Action = () => { - var showHide = _menuItems?.First(m => m.Id == "ShowHide") ?? throw new InvalidOperationException(); + SelectionItemAction showHide = _menuItems?.First(m => m.Id == "ShowHide") ?? throw new InvalidOperationException(); showHide.DisplayString = _showHidden ? "Show hidden files" : "Hide hidden files"; _showHidden = !_showHidden; }, @@ -141,7 +141,7 @@ private bool CanAccess(string path, [NotNullWhen(true)] out string[]? subdirs) { try { - var items = new DirectoryInfo(path).GetDirectories(); + DirectoryInfo[] items = new DirectoryInfo(path).GetDirectories(); if (_showHidden) { items = items.Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden)).ToArray(); @@ -224,8 +224,8 @@ public async Task ShowMenu() try { AnsiConsole.Clear(); - var menu = CreateSelection(); - var selected = await menu.ShowAsync(AnsiConsole.Console, CancellationToken.None); + SelectionPrompt menu = CreateSelection(); + SelectionItemBase selected = await menu.ShowAsync(AnsiConsole.Console, CancellationToken.None); if (selected is SelectionItemDirectory directory) { if (CanAccess(directory.Path, out string[]? subdirs)) @@ -250,7 +250,7 @@ public async Task ShowMenu() AnsiConsole.WriteException(ex); #endif AnsiConsole.WriteLine(ex.Message); - var confirm = new ConfirmationPrompt("Press a key to continue").HideChoices(); + ConfirmationPrompt confirm = new ConfirmationPrompt("Press a key to continue").HideChoices(); await confirm.ShowAsync(AnsiConsole.Console, CancellationToken.None); } } diff --git a/Source/BookGen.Shellprog/CommandCode/Cdg/SelectionItemFactory.cs b/Source/BookGen.Shellprog/CommandCode/Cdg/SelectionItemFactory.cs index d7fa5b04..daac663a 100644 --- a/Source/BookGen.Shellprog/CommandCode/Cdg/SelectionItemFactory.cs +++ b/Source/BookGen.Shellprog/CommandCode/Cdg/SelectionItemFactory.cs @@ -24,8 +24,8 @@ static string GetVolumeLabel(DriveInfo drive) return string.Empty; } - var drives = DriveInfo.GetDrives(); - foreach (var drive in drives) + DriveInfo[] drives = DriveInfo.GetDrives(); + foreach (DriveInfo drive in drives) { yield return new SelectionItemDirectory { @@ -53,7 +53,7 @@ public static IEnumerable CreateFromDirectories(string[] public static IEnumerable GetSpecialFolders() { - foreach (var specialFolder in Enum.GetValues()) + foreach (Environment.SpecialFolder specialFolder in Enum.GetValues()) { var path = Environment.GetFolderPath(specialFolder); if (!string.IsNullOrEmpty(path)) diff --git a/Source/BookGen.Shellprog/CommandCode/Organize/RuleEngine.cs b/Source/BookGen.Shellprog/CommandCode/Organize/RuleEngine.cs index 1f5ef028..4d56706c 100644 --- a/Source/BookGen.Shellprog/CommandCode/Organize/RuleEngine.cs +++ b/Source/BookGen.Shellprog/CommandCode/Organize/RuleEngine.cs @@ -24,7 +24,7 @@ public void Run(string folder, bool simulate) { foreach (var file in Directory.GetFiles(folder)) { - var foundRule = _loadedRules.FirstOrDefault(rule => rule.Key.IsMatch(file)); + KeyValuePair foundRule = _loadedRules.FirstOrDefault(rule => rule.Key.IsMatch(file)); if (foundRule.Key == null) { diff --git a/Source/BookGen.Shellprog/CommandCode/Organize/RuleLoader.cs b/Source/BookGen.Shellprog/CommandCode/Organize/RuleLoader.cs index 9277681f..1e106b84 100644 --- a/Source/BookGen.Shellprog/CommandCode/Organize/RuleLoader.cs +++ b/Source/BookGen.Shellprog/CommandCode/Organize/RuleLoader.cs @@ -57,7 +57,7 @@ private void EditFile() private OrganizeRule[] Deserialize() { - var collection = JsonSerializer.Deserialize(File.ReadAllText(_ruleFile), _options); + OrganizeRule[]? collection = JsonSerializer.Deserialize(File.ReadAllText(_ruleFile), _options); return collection?.Length > 0 ? collection : []; } } diff --git a/Source/BookGen.Shellprog/GitAutoCompleteCommand.cs b/Source/BookGen.Shellprog/GitAutoCompleteCommand.cs index c66b7944..2134c669 100644 --- a/Source/BookGen.Shellprog/GitAutoCompleteCommand.cs +++ b/Source/BookGen.Shellprog/GitAutoCompleteCommand.cs @@ -30,7 +30,7 @@ public override int Execute(IReadOnlyList context) && int.TryParse(context[0], out int index) && !string.IsNullOrEmpty(context[1])) { - var candidates = ShellAutoCompleteFilter.DoFilter(items, context[1], index); + IEnumerable candidates = ShellAutoCompleteFilter.DoFilter(items, context[1], index); #if DEBUGGING var json = System.Text.Json.JsonSerializer.Serialize(new diff --git a/Source/BookGen.Shellprog/GitCommandBase.cs b/Source/BookGen.Shellprog/GitCommandBase.cs index da8f56d6..db759a8b 100644 --- a/Source/BookGen.Shellprog/GitCommandBase.cs +++ b/Source/BookGen.Shellprog/GitCommandBase.cs @@ -44,7 +44,7 @@ protected static GitDirectoryStatus TestIfGitDir(string workDir) { string[] arguments = ["rev-parse", "--is-inside-work-tree"]; - var (exitcode, result, error) = ProcessRunner.RunProcess("git", arguments, TimeOut, workDir); + (int exitcode, string? result, string? error) = ProcessRunner.RunProcess("git", arguments, TimeOut, workDir); if (exitcode == 128 && error.Contains("detected dubious ownership")) { @@ -66,7 +66,7 @@ protected static GitDirectoryStatus TestIfGitDir(string workDir) protected static string GetGitRemote(string workDirectory) { string[] gitArguments = ["config", "--get", "remote.origin.url"]; - var (exitcode, output, error) = ProcessRunner.RunProcess("git", gitArguments, TimeOut, workDirectory); + (int exitcode, string? output, string? error) = ProcessRunner.RunProcess("git", gitArguments, TimeOut, workDirectory); return exitcode == 0 && string.IsNullOrEmpty(error) ? output : string.Empty; } @@ -76,7 +76,7 @@ protected static string GetGitRemote(string workDirectory) { string[] gitArguments = ["status", "-b", "-s", "--porcelain=2"]; - var (exitcode, output, _) = ProcessRunner.RunProcess("git", gitArguments, TimeOut, workDirectory); + (int exitcode, string? output, string _) = ProcessRunner.RunProcess("git", gitArguments, TimeOut, workDirectory); if (exitcode == 0) { return GitParser.ParseStatus(output); @@ -92,7 +92,7 @@ protected static string GetGitRemote(string workDirectory) protected void PrintUntrusted() { - var builder = new TerminalOutputBuilder() + TerminalOutputBuilder builder = new TerminalOutputBuilder() .Append(TerminalOutputBuilder.ForegroundColor.Yellow, TerminalOutputBuilder.BackgroundColor.Black, ""); _console.WriteLine(builder.ToString()); @@ -105,7 +105,7 @@ protected void PrintStatus(GitStatus? status) return; } - var builder = new TerminalOutputBuilder() + TerminalOutputBuilder builder = new TerminalOutputBuilder() .Append(TerminalOutputBuilder.ForegroundColor.Default, TerminalOutputBuilder.BackgroundColor.Green, $"({status.BranchName}) "); if (status.IncommingCommits > 0) diff --git a/Source/BookGen.Shellprog/OrganizeCommand.cs b/Source/BookGen.Shellprog/OrganizeCommand.cs index 7a70e31b..a9e1346b 100644 --- a/Source/BookGen.Shellprog/OrganizeCommand.cs +++ b/Source/BookGen.Shellprog/OrganizeCommand.cs @@ -26,7 +26,7 @@ public override int Execute(OrganizeArguments arguments, IReadOnlyList c try { var ruleLoader = new RuleLoader(arguments.Folder); - var rules = ruleLoader.LoadRules(); + IReadOnlyList rules = ruleLoader.LoadRules(); var engine = new RuleEngine(rules, _log); engine.Run(arguments.Folder, arguments.Simulate); return 0; diff --git a/Source/BookGen.Shellprog/Program.cs b/Source/BookGen.Shellprog/Program.cs index 5902f151..b4ff9451 100644 --- a/Source/BookGen.Shellprog/Program.cs +++ b/Source/BookGen.Shellprog/Program.cs @@ -11,7 +11,7 @@ using Spectre.Console; -using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); +using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); ILogger logger = loggerFactory.CreateLogger("BookGen.Shell"); @@ -22,7 +22,7 @@ ioc.AddSingleton(runnerProxy); ioc.AddSingleton(logger); -using var provider = ioc.BuildServiceProvider(); +using ServiceProvider provider = ioc.BuildServiceProvider(); CommandRunner runner = new(provider, logger, new CommandRunnerSettings { diff --git a/Source/BookGen.Shellprog/PromptCommand.cs b/Source/BookGen.Shellprog/PromptCommand.cs index c7044e9c..af0f57e3 100644 --- a/Source/BookGen.Shellprog/PromptCommand.cs +++ b/Source/BookGen.Shellprog/PromptCommand.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using BookGen.Cli.Annotations; +using BookGen.Shell.Shared; using Spectre.Console; @@ -20,10 +21,10 @@ public override int Execute(GitArguments arguments, IReadOnlyList contex { if (!string.IsNullOrEmpty(arguments.WorkDirectory)) { - var result = TestIfGitDir(arguments.WorkDirectory); + GitDirectoryStatus result = TestIfGitDir(arguments.WorkDirectory); if (result == GitDirectoryStatus.GitDirectory) { - var status = GetGitStatus(arguments.WorkDirectory); + GitStatus? status = GetGitStatus(arguments.WorkDirectory); PrintStatus(status); } else if (result == GitDirectoryStatus.UntrustedGitDirectory) diff --git a/Source/BookGen.Vfs/ApiClient.cs b/Source/BookGen.Vfs/ApiClient.cs index 5abf2f8c..90466a36 100644 --- a/Source/BookGen.Vfs/ApiClient.cs +++ b/Source/BookGen.Vfs/ApiClient.cs @@ -46,7 +46,7 @@ public async Task DownloadJsonAsync(Uri url, JsonSerializerOptions? option if (response.IsSuccessStatusCode) { - await using var stream = await response.Content.ReadAsStreamAsync(); + await using Stream stream = await response.Content.ReadAsStreamAsync(); return await JsonSerializer.DeserializeAsync(stream, options) ?? throw new InvalidOperationException($"Coudln't deserialize response as {typeof(T)}"); } @@ -62,7 +62,7 @@ public async Task DownloadFileTo(Uri url, Stream target, IProgress progres if (response.IsSuccessStatusCode) { - await using var source = await response.Content.ReadAsStreamAsync(); + await using Stream source = await response.Content.ReadAsStreamAsync(); byte[] buffer = new byte[8192]; int read = 0; diff --git a/Source/BookGen.Vfs/BookGen.Vfs.csproj b/Source/BookGen.Vfs/BookGen.Vfs.csproj index 69971531..e4ae09ae 100644 --- a/Source/BookGen.Vfs/BookGen.Vfs.csproj +++ b/Source/BookGen.Vfs/BookGen.Vfs.csproj @@ -7,6 +7,7 @@ ..\..\bin\$(Configuration)\ false preview + True diff --git a/Source/BookGen.Vfs/Extensions.cs b/Source/BookGen.Vfs/Extensions.cs index f5b767af..4824c723 100644 --- a/Source/BookGen.Vfs/Extensions.cs +++ b/Source/BookGen.Vfs/Extensions.cs @@ -17,7 +17,7 @@ public static class Extensions { public async Task DeserializeAsync(string path) { - await using var stream = fs.OpenReadStream(path); + await using Stream stream = fs.OpenReadStream(path); T? result = await JsonSerializer.DeserializeAsync(stream, JsonOptions.SerializerOptions); return result; } @@ -80,13 +80,13 @@ public async Task WriteJsonAsync(string path, JsonObject json) public async Task WriteSchema(string path) { - var node = JsonOptions.SerializerOptions.GetJsonSchemaAsNode(typeof(T), JsonOptions.ExporterOptions); + JsonNode node = JsonOptions.SerializerOptions.GetJsonSchemaAsNode(typeof(T), JsonOptions.ExporterOptions); await fs.WriteAllTextAsync(path, node.ToJsonString(JsonOptions.SerializerOptions)); } public async Task SerializeAsync(string path, T value, bool writeSchema) { - await using var stream = fs.CreateWriteStream(path); + await using Stream stream = fs.CreateWriteStream(path); await JsonSerializer.SerializeAsync(stream, value, JsonOptions.SerializerOptions); if (writeSchema) { diff --git a/Source/BookGen.Vfs/FileSystem.cs b/Source/BookGen.Vfs/FileSystem.cs index 614e6c60..af25fe12 100644 --- a/Source/BookGen.Vfs/FileSystem.cs +++ b/Source/BookGen.Vfs/FileSystem.cs @@ -29,15 +29,15 @@ public async Task CopyToAsync(string path, string destination) { static async Task CopySingleFile(string source, string destination) { - await using var sourceStream = File.OpenRead(source); - await using var destinationStream = File.Create(destination); + await using FileStream sourceStream = File.OpenRead(source); + await using FileStream destinationStream = File.Create(destination); await sourceStream.CopyToAsync(destinationStream); } var actualPath = GetAndValidateFullNameInScope(path); if (Directory.Exists(actualPath)) { - var files = Directory.EnumerateFiles(actualPath, "*.*", SearchOption.AllDirectories); + IEnumerable files = Directory.EnumerateFiles(actualPath, "*.*", SearchOption.AllDirectories); foreach (var file in files) { CreatePathIfNeeded(file); diff --git a/Source/BookGen.Vfs/Internals/JsonSchemaTransformer.cs b/Source/BookGen.Vfs/Internals/JsonSchemaTransformer.cs index 8262b81f..ddb7c69c 100644 --- a/Source/BookGen.Vfs/Internals/JsonSchemaTransformer.cs +++ b/Source/BookGen.Vfs/Internals/JsonSchemaTransformer.cs @@ -36,7 +36,7 @@ public static JsonNode TransformSchemaNode(JsonSchemaExporterContext context, Js && propertiesNode is JsonObject properties) { var requiredArray = new JsonArray(); - foreach (var property in properties) + foreach (KeyValuePair property in properties) { requiredArray.Add(property.Key); } diff --git a/Source/BookGen.Vfs/ZipBuilder.cs b/Source/BookGen.Vfs/ZipBuilder.cs index e48418a8..b0cd48f3 100644 --- a/Source/BookGen.Vfs/ZipBuilder.cs +++ b/Source/BookGen.Vfs/ZipBuilder.cs @@ -28,8 +28,8 @@ public async Task AddAsync(string entryName, Encoding encoding, CompressionLevel compressionLevel = CompressionLevel.SmallestSize) { - var entry = _archive.CreateEntry(entryName, compressionLevel); - await using var entryStream = entry.Open(); + ZipArchiveEntry entry = _archive.CreateEntry(entryName, compressionLevel); + await using Stream entryStream = entry.Open(); await using var writer = new StreamWriter(entryStream, encoding); await writer.WriteAsync(entryValue); } @@ -38,8 +38,8 @@ public async Task AddAsync(string entryName, Stream entryValue, CompressionLevel compressionLevel = CompressionLevel.SmallestSize) { - var entry = _archive.CreateEntry(entryName, compressionLevel); - await using var entryStream = entry.Open(); + ZipArchiveEntry entry = _archive.CreateEntry(entryName, compressionLevel); + await using Stream entryStream = entry.Open(); await entryValue.CopyToAsync(entryStream); } @@ -47,8 +47,8 @@ public async Task AddAsync(string entryName, byte[] entryValue, CompressionLevel compressionLevel = CompressionLevel.NoCompression) { - var entry = _archive.CreateEntry(entryName, compressionLevel); - await using var entryStream = entry.Open(); + ZipArchiveEntry entry = _archive.CreateEntry(entryName, compressionLevel); + await using Stream entryStream = entry.Open(); await entryStream.WriteAsync(entryValue, 0, entryValue.Length); } @@ -68,8 +68,8 @@ public void AddXml(string entryName, } } - var entry = _archive.CreateEntry(entryName, CompressionLevel.SmallestSize); - using var entryStream = entry.Open(); + ZipArchiveEntry entry = _archive.CreateEntry(entryName, CompressionLevel.SmallestSize); + using Stream entryStream = entry.Open(); serializer.Serialize(entryStream, instance, xnames); } diff --git a/Source/BookGen/BookGen.csproj b/Source/BookGen/BookGen.csproj index 0bcf8def..f9c52325 100644 --- a/Source/BookGen/BookGen.csproj +++ b/Source/BookGen/BookGen.csproj @@ -11,6 +11,7 @@ ..\..\Branding\icon-bookgen.ico $([System.DateTime]::UtcNow.ToString("yyyy")).$([System.DateTime]::UtcNow.ToString("MM")).$([System.DateTime]::UtcNow.ToString("dd")).0 true + True diff --git a/Source/BookGen/BuildArguments.cs b/Source/BookGen/BuildArguments.cs index aac18e9b..41f09d79 100644 --- a/Source/BookGen/BuildArguments.cs +++ b/Source/BookGen/BuildArguments.cs @@ -18,7 +18,7 @@ public sealed class BuildArguments : BookGenArgumentBase public override ValidationResult Validate(IValidationContext context) { - var originalResult = base.Validate(context); + ValidationResult originalResult = base.Validate(context); if (originalResult.IsOk && !string.IsNullOrEmpty(HostOverride) && !HostOverride.EndsWith('/')) diff --git a/Source/BookGen/Commands/AddFrontMatterCommand.cs b/Source/BookGen/Commands/AddFrontMatterCommand.cs index adca171e..38072267 100644 --- a/Source/BookGen/Commands/AddFrontMatterCommand.cs +++ b/Source/BookGen/Commands/AddFrontMatterCommand.cs @@ -17,6 +17,8 @@ using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; + namespace BookGen.Commands; [CommandName("addfrontmatter")] @@ -38,7 +40,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea var files = _writableFileSystem.GetFiles(arguments.Directory, "*.md", true).ToArray(); _logger.LogInformation("Found {count} markdown files in {directory}", files.Length, arguments.Directory); - var serializer = YamlSerializerFactory.CreateSerializer(); + ISerializer serializer = YamlSerializerFactory.CreateSerializer(); foreach (var file in files) { @@ -51,7 +53,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea _logger.LogDebug("Adding front matter to: {file}...", file); - var firstHedding = Markdown.Parse(content).OfType().FirstOrDefault(); + HeadingBlock? firstHedding = Markdown.Parse(content).OfType().FirstOrDefault(); string title = firstHedding?.Inline != null ? string.Join("", firstHedding.Inline) diff --git a/Source/BookGen/Commands/AssemblyDocument.cs b/Source/BookGen/Commands/AssemblyDocument.cs index dcfb80f0..35fc02d8 100644 --- a/Source/BookGen/Commands/AssemblyDocument.cs +++ b/Source/BookGen/Commands/AssemblyDocument.cs @@ -33,7 +33,7 @@ public class Arguments : InputOutputArguments public override int Execute(Arguments arguments, IReadOnlyList context) { - var result = XmlDocMarkdownGenerator.Generate(arguments.InputFile, arguments.OutputFile, new XmlDocMarkdownSettings + XmlDocMarkdownResult result = XmlDocMarkdownGenerator.Generate(arguments.InputFile, arguments.OutputFile, new XmlDocMarkdownSettings { IsDryRun = arguments.DryRun, IncludeObsolete = true, diff --git a/Source/BookGen/Commands/ConfigCommand.cs b/Source/BookGen/Commands/ConfigCommand.cs index 1e36ed65..f62c1f30 100644 --- a/Source/BookGen/Commands/ConfigCommand.cs +++ b/Source/BookGen/Commands/ConfigCommand.cs @@ -29,7 +29,7 @@ public override int Execute(IReadOnlyList context) { BookGenAppSettings appSettings = new(); - var data = typeof(BookGenAppSettings).GetProperties(BindingFlags.Public | BindingFlags.Instance); + PropertyInfo[] data = typeof(BookGenAppSettings).GetProperties(BindingFlags.Public | BindingFlags.Instance); if (context.Count == 0) return DisplayConfig(data, appSettings); @@ -46,7 +46,7 @@ public override int Execute(IReadOnlyList context) private int DisplaySpecific(PropertyInfo[] data, BookGenAppSettings appSettings, string property) { string[] headers = ["Name", "Type", "Value"]; - var tableData = data.Where(x => x.Name == property).ToArray(); + PropertyInfo[] tableData = data.Where(x => x.Name == property).ToArray(); if (tableData.Length != 0) return DisplayConfig(tableData, appSettings); @@ -58,7 +58,7 @@ private int DisplaySpecific(PropertyInfo[] data, BookGenAppSettings appSettings, private static int DisplayConfig(PropertyInfo[] data, BookGenAppSettings appSettings) { string[] headers = ["Name", "Type", "Value"]; - var tableData = data.Select(x => new string[] + IEnumerable tableData = data.Select(x => new string[] { x.Name, x.PropertyType.ToString(), diff --git a/Source/BookGen/Commands/GuiCommand.cs b/Source/BookGen/Commands/GuiCommand.cs index c970380f..a26e2a68 100644 --- a/Source/BookGen/Commands/GuiCommand.cs +++ b/Source/BookGen/Commands/GuiCommand.cs @@ -45,7 +45,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea figlet.Justification = Justify.Center; AnsiConsole.Write(figlet); - var path = new TextPath(_fileSystem.Scope) + TextPath path = new TextPath(_fileSystem.Scope) .RootColor(Color.Red) .SeparatorColor(Color.Green) .StemColor(Color.Blue) @@ -58,7 +58,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea AnsiConsole.WriteLine(); AnsiConsole.WriteLine(); - var selector = new SelectionPrompt() + SelectionPrompt selector = new SelectionPrompt() .Title("Select an action:") .PageSize(20) .UseConverter(mi => mi.ToString()) @@ -83,7 +83,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea new(Emoji.Known.Door, "Exit", OnExit) ]); - var selected = AnsiConsole.Prompt(selector); + MenuItem selected = AnsiConsole.Prompt(selector); return await selected.ExecuteAsync(); } diff --git a/Source/BookGen/Commands/ImgConvert.cs b/Source/BookGen/Commands/ImgConvert.cs index e76fac8d..2d213ac9 100644 --- a/Source/BookGen/Commands/ImgConvert.cs +++ b/Source/BookGen/Commands/ImgConvert.cs @@ -95,17 +95,17 @@ public override int Execute(ImgConvertArgs arguments, IReadOnlyList cont ".jpg", ".jpeg", ".png", ".webp" }; - if (!Resolution.TryParse(arguments.Resolution, CultureInfo.InvariantCulture, out var resolution)) + if (!Resolution.TryParse(arguments.Resolution, CultureInfo.InvariantCulture, out Resolution resolution)) { Console.Error.WriteLine($"Invalid resolution format: '{arguments.Resolution}'. Expected format is 'WidthxHeight'."); return ExitCodes.ArgumentsError; } - var format = Enum.Parse(arguments.Format, ignoreCase: true); + ImageFormat format = Enum.Parse(arguments.Format, ignoreCase: true); if (_fileSystem.DirectoryExists(arguments.Input)) { - var files = _fileSystem.GetFiles(arguments.Input, "*.*", false).Where(f => supportedExtensions.Contains(Path.GetExtension(f))); + IEnumerable files = _fileSystem.GetFiles(arguments.Input, "*.*", false).Where(f => supportedExtensions.Contains(Path.GetExtension(f))); Parallel.ForEach(files, file => { var outputFile = Path.Combine(arguments.Output, Path.GetFileNameWithoutExtension(file) + "." + arguments.Format); diff --git a/Source/BookGen/Commands/InstallCommand.cs b/Source/BookGen/Commands/InstallCommand.cs index f35245dd..8411688b 100644 --- a/Source/BookGen/Commands/InstallCommand.cs +++ b/Source/BookGen/Commands/InstallCommand.cs @@ -42,9 +42,9 @@ public override async Task ExecuteAsync(IReadOnlyList context) Action = InstallTerminalProfile() } }; - var selction = Terminal.SelectionMenu(menu, "Bookgen installer", "Select install options", f => f.DisplayText); + List selction = Terminal.SelectionMenu(menu, "Bookgen installer", "Select install options", f => f.DisplayText); - foreach (var item in selction) + foreach (InstallOption item in selction) { await item.Action; } @@ -66,7 +66,7 @@ private Task AddToPath() { var currentFolder = Environment.CurrentDirectory; var path = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty; - var paths = path.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); + List paths = path.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); if (paths.Contains(currentFolder, StringComparer.OrdinalIgnoreCase)) { diff --git a/Source/BookGen/Commands/LinksCommand.cs b/Source/BookGen/Commands/LinksCommand.cs index 19b84962..8e66e0e0 100644 --- a/Source/BookGen/Commands/LinksCommand.cs +++ b/Source/BookGen/Commands/LinksCommand.cs @@ -8,6 +8,7 @@ using System.Text.RegularExpressions; using Bookgen.Lib; +using Bookgen.Lib.Domain.IO; using BookGen.Cli; using BookGen.Cli.Annotations; @@ -53,7 +54,7 @@ public override async Task ExecuteAsync(LinkArguments arguments, IReadOnlyL Dictionary allLinks = new(); Dictionary badLinks = new(); - foreach (var chapter in env.TableOfContents.Chapters) + foreach (TocChapter chapter in env.TableOfContents.Chapters) { _logger.LogInformation("Scanning {chapter} for links...", chapter.Title); @@ -93,7 +94,7 @@ public override async Task ExecuteAsync(LinkArguments arguments, IReadOnlyL private async Task WriteMarkdown(Dictionary dataSet, string fileName) { MarkdownBuilder markdown = new(); - foreach (var linkData in dataSet) + foreach (KeyValuePair linkData in dataSet) { markdown.Heading(2, linkData.Key); markdown.UnorderedList(linkData.Value); @@ -108,7 +109,7 @@ private async Task WriteMarkdown(Dictionary dataSet, string fi private static IEnumerable GetLinks(string text) { - var links = Links.Matches(text); + MatchCollection links = Links.Matches(text); foreach (Match link in links) { yield return link.Value; @@ -121,14 +122,14 @@ private async Task> VerifyLinks(Has await Parallel.ForEachAsync(chapterLinks, async (link, cancellationToken) => { - using var client = CreateHttpClient(); + using HttpClient client = CreateHttpClient(); try { _logger.LogDebug("Verifying {link}...", link); using var request = new HttpRequestMessage(HttpMethod.Head, link); - using var response = await client.SendAsync(request, + using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); @@ -137,7 +138,7 @@ await Parallel.ForEachAsync(chapterLinks, async (link, cancellationToken) => response.StatusCode == HttpStatusCode.NotImplemented) { using var getRequest = new HttpRequestMessage(HttpMethod.Get, link); - using var getResponse = await client.SendAsync(getRequest, + using HttpResponseMessage getResponse = await client.SendAsync(getRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken); diff --git a/Source/BookGen/Commands/NewPageCommand.cs b/Source/BookGen/Commands/NewPageCommand.cs index 7a39025e..3ed533ce 100644 --- a/Source/BookGen/Commands/NewPageCommand.cs +++ b/Source/BookGen/Commands/NewPageCommand.cs @@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; + namespace BookGen.Commands; [CommandName("newpage")] @@ -54,7 +56,7 @@ public override int Execute(Arguments arguments, IReadOnlyList context) Tags = "", }; - var serializer = YamlSerializerFactory.CreateSerializer(); + ISerializer serializer = YamlSerializerFactory.CreateSerializer(); var yaml = serializer.Serialize(frontMatter); diff --git a/Source/BookGen/Commands/ShellCommand.cs b/Source/BookGen/Commands/ShellCommand.cs index 97dda178..6eb34d48 100644 --- a/Source/BookGen/Commands/ShellCommand.cs +++ b/Source/BookGen/Commands/ShellCommand.cs @@ -68,7 +68,7 @@ internal IEnumerable DoComplete(IReadOnlyList args) if (words.Length <= 1) return items; - var candidate = items.Where(arg => arg.StartsWith(words.Last(), StringComparison.OrdinalIgnoreCase)); + IEnumerable candidate = items.Where(arg => arg.StartsWith(words.Last(), StringComparison.OrdinalIgnoreCase)); if (candidate.Any()) return candidate; diff --git a/Source/BookGen/Commands/StatsCommand.cs b/Source/BookGen/Commands/StatsCommand.cs index 6060acc3..5ac5bcfd 100644 --- a/Source/BookGen/Commands/StatsCommand.cs +++ b/Source/BookGen/Commands/StatsCommand.cs @@ -35,7 +35,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea _soruce.Scope = arguments.Directory; using var env = new BookEnvironment(_soruce, _soruce); - var status = await env.Initialize(arguments.ConfigOverlay); + EnvironmentStatus status = await env.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { diff --git a/Source/BookGen/Commands/SubCommandsCommand.cs b/Source/BookGen/Commands/SubCommandsCommand.cs index 7066e0d7..f48fbb1c 100644 --- a/Source/BookGen/Commands/SubCommandsCommand.cs +++ b/Source/BookGen/Commands/SubCommandsCommand.cs @@ -27,7 +27,7 @@ public SubCommandsCommand(ICommandRunnerProxy runnerProxy) public override int Execute(IReadOnlyList context) { Terminal.Header("Available sub commands:"); - foreach (var commandGroup in _commands) + foreach (IGrouping commandGroup in _commands) { AnsiConsole.WriteLine(commandGroup.Key); foreach (var command in commandGroup) diff --git a/Source/BookGen/Commands/TemplatesCommand.cs b/Source/BookGen/Commands/TemplatesCommand.cs index 5fbdb75e..225e483c 100644 --- a/Source/BookGen/Commands/TemplatesCommand.cs +++ b/Source/BookGen/Commands/TemplatesCommand.cs @@ -59,7 +59,7 @@ private void ListTempates() Terminal.List(_defaultTemplates); Terminal.Header("Single page templates:", blankLineBefore: 1); - var singlePage = _assetSource.AssetNames.Where(n => n.EndsWith(".template", StringComparison.OrdinalIgnoreCase)).Order(); + IOrderedEnumerable singlePage = _assetSource.AssetNames.Where(n => n.EndsWith(".template", StringComparison.OrdinalIgnoreCase)).Order(); Terminal.List(singlePage); } } diff --git a/Source/BookGen/Commands/TerminalInstallCommand.cs b/Source/BookGen/Commands/TerminalInstallCommand.cs index 902ac4af..7291a66a 100644 --- a/Source/BookGen/Commands/TerminalInstallCommand.cs +++ b/Source/BookGen/Commands/TerminalInstallCommand.cs @@ -39,7 +39,7 @@ public override async Task ExecuteAsync(TerminalInstallArguments arguments, if (arguments.CheckTerminalInstall) { - var installReult = InstallDetector.GetInstallResult(); + InstallResult installReult = InstallDetector.GetInstallResult(); return installReult.IsWindowsTerminalInstalled ? ExitCodes.Success : ExitCodes.GeneralError; } diff --git a/Source/BookGen/Commands/ToolsCommand.cs b/Source/BookGen/Commands/ToolsCommand.cs index 5c64cdf7..7f65ddbe 100644 --- a/Source/BookGen/Commands/ToolsCommand.cs +++ b/Source/BookGen/Commands/ToolsCommand.cs @@ -52,13 +52,13 @@ public override async Task ExecuteAsync(IReadOnlyList context) AnsiConsole.Clear(); AnsiConsole.Write(new FigletText("Tool installer")); - var selectedItems = Terminal.SelectionMenu(items: _tooldownloaders, + List selectedItems = Terminal.SelectionMenu(items: _tooldownloaders, title: "Select tools to download", instructions: "[grey](Press [blue][/] to toggle a tool for download, [green][/] to accept)[/]", displaySelector: ToSDisplayString); - foreach (var selected in selectedItems) + foreach (TooldownloaderBase selected in selectedItems) { _logger.LogInformation("Installing {tool} ...", selected.ToolInfo.Name); var ui = new ToolDownloadUi(); diff --git a/Source/BookGen/Extensions.cs b/Source/BookGen/Extensions.cs index 89031430..504c974b 100644 --- a/Source/BookGen/Extensions.cs +++ b/Source/BookGen/Extensions.cs @@ -12,7 +12,7 @@ public bool IsValidTemplateFile(string templateFile) if (string.IsNullOrEmpty(templateFile)) return true; - var source = context.Resolve(); + IAssetSource source = context.Resolve(); if (source.AssetNames.Contains(templateFile)) return true; diff --git a/Source/BookGen/Infrastructure/HelpProvider.cs b/Source/BookGen/Infrastructure/HelpProvider.cs index ddeee6bc..36dd5981 100644 --- a/Source/BookGen/Infrastructure/HelpProvider.cs +++ b/Source/BookGen/Infrastructure/HelpProvider.cs @@ -32,7 +32,7 @@ public HelpProvider(ILogger log, ICommandRunnerProxy nameProvider) private void LoadHelpData() { - var lines = + IReadOnlyList lines = ResourceHandler.GetResourceFileLines("Resources/Commands.md"); List chapterData = new(50); diff --git a/Source/BookGen/Infrastructure/HelpRenderer.cs b/Source/BookGen/Infrastructure/HelpRenderer.cs index 0ee95062..1546d030 100644 --- a/Source/BookGen/Infrastructure/HelpRenderer.cs +++ b/Source/BookGen/Infrastructure/HelpRenderer.cs @@ -7,6 +7,7 @@ using Markdig; using Markdig.Parsers; +using Markdig.Syntax; namespace BookGen.Infrastructure; @@ -22,7 +23,7 @@ public HelpRenderer() public void RenderHelp(IEnumerable article) { string md = string.Join(Environment.NewLine, article); - var document = MarkdownParser.Parse(md, _terminalPipeLine); + MarkdownDocument document = MarkdownParser.Parse(md, _terminalPipeLine); using var writer = new StringWriter(); var renderer = new TerminalRenderer(writer, new RenderOptions()); diff --git a/Source/BookGen/Infrastructure/Loging/ConsoleLogProvider.cs b/Source/BookGen/Infrastructure/Loging/ConsoleLogProvider.cs index 40e128ab..d49f4fe4 100644 --- a/Source/BookGen/Infrastructure/Loging/ConsoleLogProvider.cs +++ b/Source/BookGen/Infrastructure/Loging/ConsoleLogProvider.cs @@ -36,7 +36,7 @@ public ILogger CreateLogger(string categoryName) public void Dispose() { - foreach (var logger in _loggers) + foreach (KeyValuePair logger in _loggers) { logger.Value.Dispose(); } diff --git a/Source/BookGen/Infrastructure/Terminal/Terminal.cs b/Source/BookGen/Infrastructure/Terminal/Terminal.cs index 9776b8e5..3d983ec7 100644 --- a/Source/BookGen/Infrastructure/Terminal/Terminal.cs +++ b/Source/BookGen/Infrastructure/Terminal/Terminal.cs @@ -29,14 +29,14 @@ public static void Table(string[] headers, IEnumerable rows) public static void BarChart(IDictionary items, string title = "") { - var chart = new BarChart() + BarChart chart = new BarChart() .Width(Console.WindowWidth) .Label(title) .CenterLabel(); _palette.Reset(); - foreach (var item in items) + foreach (KeyValuePair item in items) { chart.AddItem(item.Key, item.Value, _palette.GetNextColor()); } @@ -46,10 +46,10 @@ public static void BarChart(IDictionary items, string title = "" public static void BreakDownChart(IDictionary items, string title, bool descendingOrder = true) { - var rule = new Rule(title).Centered(); + Rule rule = new Rule(title).Centered(); AnsiConsole.Write(rule); - var chart = new BreakdownChart() + BreakdownChart chart = new BreakdownChart() .Width(Console.WindowWidth); _palette.Reset(); @@ -58,7 +58,7 @@ public static void BreakDownChart(IDictionary items, string titl ? items.OrderByDescending(x => x.Value) : items; - foreach (var item in data) + foreach (KeyValuePair item in data) { chart.AddItem(item.Key, item.Value, _palette.GetNextColor()); } @@ -84,7 +84,7 @@ public static bool Confirm(string message) public static List SelectionMenu(IEnumerable items, string title, string instructions, Func displaySelector) where T : notnull { - var prompt = new MultiSelectionPrompt() + MultiSelectionPrompt prompt = new MultiSelectionPrompt() .Title(title) .PageSize(15) .InstructionsText(instructions) diff --git a/Source/BookGen/Infrastructure/Tools/Digest.cs b/Source/BookGen/Infrastructure/Tools/Digest.cs index e84a7ff5..37eda317 100644 --- a/Source/BookGen/Infrastructure/Tools/Digest.cs +++ b/Source/BookGen/Infrastructure/Tools/Digest.cs @@ -3,6 +3,7 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using System.Security.Cryptography; @@ -35,35 +36,35 @@ private static unsafe bool Compare(byte[] result, byte[] bytes) if (length == 32) // 256-bit { - var va = Avx.LoadVector256(pA); - var vb = Avx.LoadVector256(pB); + Vector256 va = Avx.LoadVector256(pA); + Vector256 vb = Avx.LoadVector256(pB); - var cmp = Avx2.CompareEqual(va, vb); + Vector256 cmp = Avx2.CompareEqual(va, vb); return Avx2.MoveMask(cmp) == -1; } else if (length == 48) // 384-bit { - var va1 = Avx.LoadVector256(pA); // first 32 bytes - var vb1 = Avx.LoadVector256(pB); + Vector256 va1 = Avx.LoadVector256(pA); // first 32 bytes + Vector256 vb1 = Avx.LoadVector256(pB); - var va2 = Sse2.LoadVector128(pA + 32); // remaining 16 bytes - var vb2 = Sse2.LoadVector128(pB + 32); + Vector128 va2 = Sse2.LoadVector128(pA + 32); // remaining 16 bytes + Vector128 vb2 = Sse2.LoadVector128(pB + 32); - var cmp1 = Avx2.CompareEqual(va1, vb1); - var cmp2 = Sse2.CompareEqual(va2, vb2); + Vector256 cmp1 = Avx2.CompareEqual(va1, vb1); + Vector128 cmp2 = Sse2.CompareEqual(va2, vb2); return Avx2.MoveMask(cmp1) == -1 && Sse2.MoveMask(cmp2) == 0xFFFF; } else if (length == 64) // 512-bit { - var va1 = Avx.LoadVector256(pA); - var vb1 = Avx.LoadVector256(pB); + Vector256 va1 = Avx.LoadVector256(pA); + Vector256 vb1 = Avx.LoadVector256(pB); - var va2 = Avx.LoadVector256(pA + 32); - var vb2 = Avx.LoadVector256(pB + 32); + Vector256 va2 = Avx.LoadVector256(pA + 32); + Vector256 vb2 = Avx.LoadVector256(pB + 32); - var cmp1 = Avx2.CompareEqual(va1, vb1); - var cmp2 = Avx2.CompareEqual(va2, vb2); + Vector256 cmp1 = Avx2.CompareEqual(va1, vb1); + Vector256 cmp2 = Avx2.CompareEqual(va2, vb2); return Avx2.MoveMask(cmp1) == -1 && Avx2.MoveMask(cmp2) == -1; } diff --git a/Source/BookGen/Infrastructure/Tools/Extractor.cs b/Source/BookGen/Infrastructure/Tools/Extractor.cs index b03fc0f4..b2bcbe86 100644 --- a/Source/BookGen/Infrastructure/Tools/Extractor.cs +++ b/Source/BookGen/Infrastructure/Tools/Extractor.cs @@ -22,7 +22,7 @@ public static async Task Copy(IDownloadUi ui, Stream stream, string folderName, if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); - await using var targetStream = File.Create(outputPath); + await using FileStream targetStream = File.Create(outputPath); await stream.CopyToAsync(targetStream, CancellationToken.None); ui.Report(stream.Length); } @@ -54,7 +54,7 @@ static string GetEntryOutputPath(string targetFolder, TarEntry entry) Directory.CreateDirectory(directory); } - await using var targetStream = File.Create(outputPath); + await using FileStream targetStream = File.Create(outputPath); if (entry.DataStream != null) { await entry.DataStream.CopyToAsync(targetStream, CancellationToken.None); @@ -73,7 +73,7 @@ static string GetEntryOutputPath(string targetFolder, ZipArchiveEntry entry) ui.BeginNew("Extracting...", archive.Entries.Sum(e => e.Length)); - foreach (var entry in archive.Entries) + foreach (ZipArchiveEntry entry in archive.Entries) { if (string.IsNullOrEmpty(entry.Name)) { @@ -88,8 +88,8 @@ static string GetEntryOutputPath(string targetFolder, ZipArchiveEntry entry) Directory.CreateDirectory(directory); } - await using var targetStream = File.Create(outputPath); - await using var source = entry.Open(); + await using FileStream targetStream = File.Create(outputPath); + await using Stream source = entry.Open(); await source.CopyToAsync(targetStream, CancellationToken.None); ui.Report(entry.Length); diff --git a/Source/BookGen/Program.cs b/Source/BookGen/Program.cs index 98408469..a48ae3ff 100644 --- a/Source/BookGen/Program.cs +++ b/Source/BookGen/Program.cs @@ -19,7 +19,7 @@ ProgramInfo info = new(); -var argumentList = ProgramConfigurator.ParseGeneralArgs(args, info); +List argumentList = ProgramConfigurator.ParseGeneralArgs(args, info); using ILoggerFactory factory = LoggerFactory .Create(builder => @@ -54,7 +54,7 @@ ioc.AddTransient(); ioc.AddTransient(); -using var provider = ioc.BuildServiceProvider(); +using ServiceProvider provider = ioc.BuildServiceProvider(); CommandRunner runner = new(provider, logger, new CommandRunnerSettings { diff --git a/Source/BookGen/Tooldownloaders/TooldownloaderBase.cs b/Source/BookGen/Tooldownloaders/TooldownloaderBase.cs index 4672f62b..ae8355a8 100644 --- a/Source/BookGen/Tooldownloaders/TooldownloaderBase.cs +++ b/Source/BookGen/Tooldownloaders/TooldownloaderBase.cs @@ -41,9 +41,9 @@ protected virtual Task Extract(IDownloadUi ui, Stream stream) public async Task DownloadToolAsync(IDownloadUi ui) { var downloadUrl = new Uri($"https://api.github.com/repos/{ToolInfo.RepoOwner}/{ToolInfo.RepoName}/releases"); - var releases = await _apiClient.DownloadJsonAsync(downloadUrl); + Release[] releases = await _apiClient.DownloadJsonAsync(downloadUrl); - var latestRelease = GetReleaseAsset(releases.SelectMany(a => a.Assets)); + ReleaseAsset? latestRelease = GetReleaseAsset(releases.SelectMany(a => a.Assets)); if (latestRelease == null) { @@ -53,7 +53,7 @@ public async Task DownloadToolAsync(IDownloadUi ui) ui.BeginNew($"Downloading {latestRelease.Name}...", latestRelease.Size); - await using var stream = _memoryStreamManager.GetStream(); + await using RecyclableMemoryStream stream = _memoryStreamManager.GetStream(); try { diff --git a/Source/Bookgen.Lib/AppSettings/AppSettingsBase.cs b/Source/Bookgen.Lib/AppSettings/AppSettingsBase.cs index fd721f24..50a58d03 100644 --- a/Source/Bookgen.Lib/AppSettings/AppSettingsBase.cs +++ b/Source/Bookgen.Lib/AppSettings/AppSettingsBase.cs @@ -85,7 +85,7 @@ private void LoadFromFile() if (loaded == null) return; - foreach (var item in loaded) + foreach (KeyValuePair item in loaded) { if (_storage.ContainsKey(item.Key)) _storage[item.Key] = item.Value; diff --git a/Source/Bookgen.Lib/BookEnvironment.cs b/Source/Bookgen.Lib/BookEnvironment.cs index 0973e370..93f188b1 100644 --- a/Source/Bookgen.Lib/BookEnvironment.cs +++ b/Source/Bookgen.Lib/BookEnvironment.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Nodes; using Bookgen.Lib.Confighandling; using Bookgen.Lib.Domain.IO; @@ -87,13 +88,13 @@ public async Task Initialize(string configOverlay) return status; } - var baseConfig = await _source.ReadJsonAsync(FileNameConstants.ConfigFile); + JsonObject baseConfig = await _source.ReadJsonAsync(FileNameConstants.ConfigFile); JsonMerger configMerger = new JsonMerger(baseConfig); if (!string.IsNullOrEmpty(configOverlay)) { - var overlayConfig = await _source.ReadJsonAsync(configOverlay); + JsonObject overlayConfig = await _source.ReadJsonAsync(configOverlay); configMerger.Merge(overlayConfig); } @@ -133,7 +134,7 @@ public async Task Initialize(string configOverlay) public bool TryGetAsset(string name, [NotNullWhen(true)] out string? content) { - foreach (var assetsource in _assets) + foreach (IAssetSource assetsource in _assets) { if (assetsource.TryGetAsset(name, out content)) { @@ -147,7 +148,7 @@ public bool TryGetAsset(string name, [NotNullWhen(true)] out string? content) public byte[] GetBinaryAsset(string name) { - foreach (var assetsource in _assets) + foreach (IAssetSource assetsource in _assets) { try { diff --git a/Source/Bookgen.Lib/BookStatFactory.cs b/Source/Bookgen.Lib/BookStatFactory.cs index 7689d227..f4d16904 100644 --- a/Source/Bookgen.Lib/BookStatFactory.cs +++ b/Source/Bookgen.Lib/BookStatFactory.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using Bookgen.Lib.Domain; +using Bookgen.Lib.Domain.IO; using Bookgen.Lib.Internals; using Bookgen.Lib.Pipeline; @@ -17,18 +18,18 @@ public static async Task CreateBookStat(IBookEnvironment environment, { BookStat stat = new(); - foreach (var chapter in environment.TableOfContents.Chapters) + foreach (TocChapter chapter in environment.TableOfContents.Chapters) { stat.ChapterSizes[chapter.Title] = 0; foreach (var file in chapter.Files) { - var sourceFile = await environment.Source.GetSourceFile(file, logger); + SourceFile sourceFile = await environment.Source.GetSourceFile(file, logger); long size = environment.Source.GetFileSize(file); stat.ChapterSizes[chapter.Title] += size; ProcessCodeBlocks(stat, sourceFile); - var (lineCount, wordCount, characterCount) = GetFileStats(sourceFile); + (long lineCount, long wordCount, long characterCount) = GetFileStats(sourceFile); stat.LineCount += lineCount; stat.WordCount += wordCount; stat.TotalSize += size; @@ -75,7 +76,7 @@ private static (long lineCount, long wordCount, long characterCount) GetFileStat string? line; while ((line = reader.ReadLine()) != null) { - var lineStats = GetLineStats(line.Where(c => !IsMarkdownChar(c))); + (int length, int words) lineStats = GetLineStats(line.Where(c => !IsMarkdownChar(c))); lineCount += (lineStats.length / 80) + (lineStats.length % 80) > 0 ? 1 : 0; wordCount += lineStats.words; characterCount += lineStats.length; diff --git a/Source/Bookgen.Lib/Bookgen.Lib.csproj b/Source/Bookgen.Lib/Bookgen.Lib.csproj index 5b8ae154..69f1c965 100644 --- a/Source/Bookgen.Lib/Bookgen.Lib.csproj +++ b/Source/Bookgen.Lib/Bookgen.Lib.csproj @@ -8,6 +8,7 @@ ..\..\bin\$(Configuration)\ false true + True diff --git a/Source/Bookgen.Lib/Confighandling/ConfigUpgrader.cs b/Source/Bookgen.Lib/Confighandling/ConfigUpgrader.cs index 7d9418d8..e2f3f9ef 100644 --- a/Source/Bookgen.Lib/Confighandling/ConfigUpgrader.cs +++ b/Source/Bookgen.Lib/Confighandling/ConfigUpgrader.cs @@ -40,7 +40,7 @@ public async Task Init(IReadOnlyFileSystem sourceFolder) _tocJson = await sourceFolder.ReadJsonAsync(FileNameConstants.TableOfContents); _configJson = await sourceFolder.ReadJsonAsync(FileNameConstants.ConfigFile); - var version = _configJson["VersionTag"] + JsonNode version = _configJson["VersionTag"] ?? throw new InvalidOperationException("Failed to determine version"); _sourceVersion = 0; @@ -60,9 +60,9 @@ public async Task Init(IReadOnlyFileSystem sourceFolder) public static async Task IsUpgradeNeeded(IReadOnlyFileSystem sourceFolder) { - var configJson = await sourceFolder.ReadJsonAsync(FileNameConstants.ConfigFile); + JsonObject configJson = await sourceFolder.ReadJsonAsync(FileNameConstants.ConfigFile); - var version = configJson["VersionTag"] + JsonNode version = configJson["VersionTag"] ?? throw new InvalidOperationException("Failed to determine version"); if (version is not JsonValue jsonValue @@ -94,7 +94,7 @@ public static async Task IsUpgradeNeeded(IReadOnlyFileSystem sourceFolder) bool tocModifed = false; bool configModified = false; - foreach (var upgrader in upgraders) + foreach (UpgradeBase upgrader in upgraders) { _logger.LogInformation("Upgrading from version {from} to {to}", upgrader.VersionTagInfo.From, upgrader.VersionTagInfo.To); tocModifed |= upgrader.UpgradeToc(_tocJson); @@ -121,7 +121,7 @@ public static async Task IsUpgradeNeeded(IReadOnlyFileSystem sourceFolder) private List SelectUpgrades(int from, int to) { List selectedUpgrades = new List(); - foreach (var upgrader in _upgrades) + foreach (UpgradeBase upgrader in _upgrades) { if (upgrader.VersionTagInfo.From >= from && upgrader.VersionTagInfo.To <= to) { diff --git a/Source/Bookgen.Lib/Confighandling/JsonMerger.cs b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs index e8827c8d..f4e9c649 100644 --- a/Source/Bookgen.Lib/Confighandling/JsonMerger.cs +++ b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs @@ -32,10 +32,10 @@ public JsonMerger(JsonObject baseObject) { //NOTE: We must materialize the set (e.g. to an Array), and then clear the merge array so the node can then be // re-assigned to the target/base Json; clearing the Object seems to be the most efficient approach... - var mergeNodesArray = jsonMergeObj.ToArray(); + KeyValuePair[] mergeNodesArray = jsonMergeObj.ToArray(); jsonMergeObj.Clear(); - foreach (var prop in mergeNodesArray) + foreach (KeyValuePair prop in mergeNodesArray) { if (mergeIfAlreadyExists || !jsonBaseObj.ContainsKey(prop.Key)) jsonBaseObj[prop.Key] = jsonBaseObj[prop.Key] switch @@ -53,9 +53,9 @@ JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray { //NOTE: We must materialize the set (e.g. to an Array), and then clear the merge array, // so they can then be re-assigned to the target/base Json... - var mergeNodesArray = jsonMergeArray.ToArray(); + JsonNode?[] mergeNodesArray = jsonMergeArray.ToArray(); jsonMergeArray.Clear(); - foreach (var mergeNode in mergeNodesArray) jsonBaseArray.Add(mergeNode); + foreach (JsonNode? mergeNode in mergeNodesArray) jsonBaseArray.Add(mergeNode); break; } default: @@ -70,7 +70,7 @@ JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray public void Merge(JsonObject overlay) { - var result = Merge(_baseObject, overlay); + JsonNode? result = Merge(_baseObject, overlay); if (result is JsonObject mergedObj) { _baseObject = mergedObj; diff --git a/Source/Bookgen.Lib/Confighandling/JsonObjectExtensions.cs b/Source/Bookgen.Lib/Confighandling/JsonObjectExtensions.cs index c5bb294f..e979ef34 100644 --- a/Source/Bookgen.Lib/Confighandling/JsonObjectExtensions.cs +++ b/Source/Bookgen.Lib/Confighandling/JsonObjectExtensions.cs @@ -12,7 +12,7 @@ internal static class JsonObjectExtensions { public static JsonObject GetSubObjectOrThrow(this JsonObject jsonObject, string propertyName) { - if (jsonObject.TryGetPropertyValue(propertyName, out var subObjectNode) && + if (jsonObject.TryGetPropertyValue(propertyName, out JsonNode? subObjectNode) && subObjectNode is JsonObject subObject) { return subObject; @@ -22,7 +22,7 @@ public static JsonObject GetSubObjectOrThrow(this JsonObject jsonObject, string public static JsonArray GetSubArrayOrThrow(this JsonObject jsonObject, string propertyName) { - if (jsonObject.TryGetPropertyValue(propertyName, out var subArrayNode) && + if (jsonObject.TryGetPropertyValue(propertyName, out JsonNode? subArrayNode) && subArrayNode is JsonArray subArray) { return subArray; diff --git a/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyConfig.cs b/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyConfig.cs index 99711d54..d3ed2ce8 100644 --- a/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyConfig.cs +++ b/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyConfig.cs @@ -3,6 +3,8 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using Bookgen.Lib.Domain.IO.Legacy; + using BookGen.Vfs; using Microsoft.Extensions.Logging; @@ -21,7 +23,7 @@ public async Task ExecuteAsync(IWritableFileSystem foler, MigrationState s return false; } - var config = await foler.DeserializeAsync(file); + Config? config = await foler.DeserializeAsync(file); if (config == null) { logger.LogError("Failed to load legacy config file"); diff --git a/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyTags.cs b/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyTags.cs index 837be98d..985d977f 100644 --- a/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyTags.cs +++ b/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyTags.cs @@ -20,7 +20,7 @@ public async Task ExecuteAsync(IWritableFileSystem foler, MigrationState s return false; } - var tags = await foler.DeserializeAsync>(file); + Dictionary? tags = await foler.DeserializeAsync>(file); if (tags == null) { diff --git a/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyToc.cs b/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyToc.cs index 6be33ec9..50ee4f8f 100644 --- a/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyToc.cs +++ b/Source/Bookgen.Lib/Confighandling/LegacyMigration/LoadLegacyToc.cs @@ -33,7 +33,7 @@ static void InsertChapter(ToC toc, ref string? currentchapter, ref List? c var parsed = new ToC(); parsed.RawMarkdown = content; MarkdownPipeline? pipeline = new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub).Build(); - var doc = Markdig.Markdown.Parse(content, pipeline); + MarkdownDocument doc = Markdig.Markdown.Parse(content, pipeline); string? chapterTitle = string.Empty; var chapterLinks = new List(); diff --git a/Source/Bookgen.Lib/Confighandling/LegacyMigration/MigrateFiles.cs b/Source/Bookgen.Lib/Confighandling/LegacyMigration/MigrateFiles.cs index 525f6a18..dcbf0c97 100644 --- a/Source/Bookgen.Lib/Confighandling/LegacyMigration/MigrateFiles.cs +++ b/Source/Bookgen.Lib/Confighandling/LegacyMigration/MigrateFiles.cs @@ -6,11 +6,14 @@ using System.Text; using Bookgen.Lib.Domain.IO; +using Bookgen.Lib.Domain.IO.Legacy; using BookGen.Vfs; using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; + namespace Bookgen.Lib.Confighandling.LegacyMigration; internal class MigrateFiles : IMigrationStep @@ -18,11 +21,11 @@ internal class MigrateFiles : IMigrationStep public async Task ExecuteAsync(IWritableFileSystem foler, MigrationState state, ILogger logger) { logger.LogInformation("Migrating files to add front matter..."); - var serializer = YamlSerializerFactory.CreateSerializer(); + ISerializer serializer = YamlSerializerFactory.CreateSerializer(); foreach (var chapter in state.LegacyToc.Chapters) { - foreach (var link in state.LegacyToc.GetLinksForChapter(chapter)) + foreach (Link link in state.LegacyToc.GetLinksForChapter(chapter)) { logger.LogDebug("Migrating file: {file}", link.Url); diff --git a/Source/Bookgen.Lib/Confighandling/LegacyMigration/Migrator.cs b/Source/Bookgen.Lib/Confighandling/LegacyMigration/Migrator.cs index aebe23a3..d6ca3981 100644 --- a/Source/Bookgen.Lib/Confighandling/LegacyMigration/Migrator.cs +++ b/Source/Bookgen.Lib/Confighandling/LegacyMigration/Migrator.cs @@ -28,7 +28,7 @@ public static async Task Migrate(IWritableFileSystem folder, ILogger logge try { - foreach (var step in steps) + foreach (IMigrationStep step in steps) { bool result = await step.ExecuteAsync(folder, state, logger); if (!result) diff --git a/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2003To2004.cs b/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2003To2004.cs index b05cab18..c01fc7bc 100644 --- a/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2003To2004.cs +++ b/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2003To2004.cs @@ -25,7 +25,7 @@ public override bool UpgradeToc(JsonObject tocFile) { JsonArray chaptersArray = tocFile.GetSubArrayOrThrow("Chapters"); - foreach (var chapterNode in chaptersArray) + foreach (JsonNode? chapterNode in chaptersArray) { if (chapterNode is JsonObject chapter) { diff --git a/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2004To2005.cs b/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2004To2005.cs index 37164a2d..5aaa91bf 100644 --- a/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2004To2005.cs +++ b/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2004To2005.cs @@ -24,7 +24,7 @@ public void UpdateImgeObject(JsonObject buildConfig) { JsonObject imgObject = buildConfig.GetSubObjectOrThrow("Images"); - bool oldvalue = imgObject.TryGetPropertyValue("ResizeAndRecodeImagesToWebp", out var webpValue) + bool oldvalue = imgObject.TryGetPropertyValue("ResizeAndRecodeImagesToWebp", out JsonNode? webpValue) && webpValue is JsonValue webpJsonValue && webpJsonValue.GetValue(); diff --git a/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2005To2006.cs b/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2005To2006.cs index f1f4f70d..7c4057d7 100644 --- a/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2005To2006.cs +++ b/Source/Bookgen.Lib/Confighandling/UpgradeSteps/FromVersion2005To2006.cs @@ -24,7 +24,7 @@ public void UpdateImgeObject(JsonObject buildConfig) { JsonObject imgObject = buildConfig.GetSubObjectOrThrow("Images"); - int oldvalue = imgObject.TryGetPropertyValue("WebpQuality", out var qualityValue) + int oldvalue = imgObject.TryGetPropertyValue("WebpQuality", out JsonNode? qualityValue) && qualityValue is JsonValue qualityJsonValue ? qualityJsonValue.GetValue() : throw new InvalidOperationException("WebpQuality property not found or is not an integer."); diff --git a/Source/Bookgen.Lib/Domain/IO/TableOfContents.cs b/Source/Bookgen.Lib/Domain/IO/TableOfContents.cs index c61eb924..9c962247 100644 --- a/Source/Bookgen.Lib/Domain/IO/TableOfContents.cs +++ b/Source/Bookgen.Lib/Domain/IO/TableOfContents.cs @@ -39,7 +39,7 @@ public IEnumerable GetFiles(bool withIndex = false) yield return IndexFile; } - foreach (var chapter in Chapters) + foreach (TocChapter chapter in Chapters) { if (chapter.Files != null) { diff --git a/Source/Bookgen.Lib/Domain/Validation/FileExistsAttribute.cs b/Source/Bookgen.Lib/Domain/Validation/FileExistsAttribute.cs index 133783a6..12633d8f 100644 --- a/Source/Bookgen.Lib/Domain/Validation/FileExistsAttribute.cs +++ b/Source/Bookgen.Lib/Domain/Validation/FileExistsAttribute.cs @@ -16,7 +16,7 @@ internal sealed class FileExistsAttribute : ValidationAttribute { protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - var folder = validationContext.GetRequiredService(); + IReadOnlyFileSystem folder = validationContext.GetRequiredService(); if (value is IEnumerable files) { diff --git a/Source/Bookgen.Lib/Domain/Validation/WhenNotEmptyFileMustExistAttribute.cs b/Source/Bookgen.Lib/Domain/Validation/WhenNotEmptyFileMustExistAttribute.cs index dcc77bcd..221806e9 100644 --- a/Source/Bookgen.Lib/Domain/Validation/WhenNotEmptyFileMustExistAttribute.cs +++ b/Source/Bookgen.Lib/Domain/Validation/WhenNotEmptyFileMustExistAttribute.cs @@ -16,7 +16,7 @@ internal sealed class WhenNotEmptyFileMustExistAttribute : ValidationAttribute { protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - var folder = validationContext.GetRequiredService(); + IReadOnlyFileSystem folder = validationContext.GetRequiredService(); if (value is not string @string) throw new InvalidOperationException($"{nameof(NotNullOrWhiteSpaceAttribute)} works with {typeof(string)} properties"); diff --git a/Source/Bookgen.Lib/Http/HttpServer.cs b/Source/Bookgen.Lib/Http/HttpServer.cs index 845ac984..260a17d5 100644 --- a/Source/Bookgen.Lib/Http/HttpServer.cs +++ b/Source/Bookgen.Lib/Http/HttpServer.cs @@ -139,7 +139,7 @@ public void AddRoute(ApiMetaData metaData, RequestDelegate handler) public void AddRoutes(IReadOnlyDictionary routes) { - foreach (var route in routes) + foreach (KeyValuePair route in routes) { AddRoute(route.Key, route.Value); } @@ -147,7 +147,7 @@ public void AddRoutes(IReadOnlyDictionary routes) public IEnumerable GetListenUrls() { - foreach (var (adress, _) in GetIpAdresses()) + foreach ((IPAddress? adress, IPAddress _) in GetIpAdresses()) { yield return $"http://{adress}:{Port}"; } @@ -159,12 +159,12 @@ public IEnumerable GetListenUrls() .Where(i => i.AddressFamily == AddressFamily.InterNetwork) .ToHashSet(); - var ifaceAddrs = NetworkInterface.GetAllNetworkInterfaces() + IEnumerable ifaceAddrs = NetworkInterface.GetAllNetworkInterfaces() .Where(i => i.OperationalStatus == OperationalStatus.Up) .SelectMany(x => x.GetIPProperties().UnicastAddresses) .Where(x => ipAdresses.Contains(x.Address)); - foreach (var adress in ifaceAddrs) + foreach (UnicastIPAddressInformation? adress in ifaceAddrs) { yield return (adress.Address, adress.IPv4Mask); } diff --git a/Source/Bookgen.Lib/Http/PageFactory.cs b/Source/Bookgen.Lib/Http/PageFactory.cs index c95500d6..0ab31d56 100644 --- a/Source/Bookgen.Lib/Http/PageFactory.cs +++ b/Source/Bookgen.Lib/Http/PageFactory.cs @@ -13,7 +13,7 @@ internal static class PageFactory { private static string GetResource(string resoruceName) { - using var stream = typeof(PageFactory).Assembly.GetManifestResourceStream(resoruceName) + using Stream stream = typeof(PageFactory).Assembly.GetManifestResourceStream(resoruceName) ?? throw new UnreachableException("Error page template was null"); using var reader = new StreamReader(stream); diff --git a/Source/Bookgen.Lib/Http/ServerFactory.cs b/Source/Bookgen.Lib/Http/ServerFactory.cs index bb8d73cc..fb2890b4 100644 --- a/Source/Bookgen.Lib/Http/ServerFactory.cs +++ b/Source/Bookgen.Lib/Http/ServerFactory.cs @@ -19,7 +19,7 @@ private static int ChoosePort(int @default = HostingPort) { IPGlobalProperties ipProps = IPGlobalProperties.GetIPGlobalProperties(); - var props = ipProps.GetActiveTcpConnections(); + TcpConnectionInformation[] props = ipProps.GetActiveTcpConnections(); IEnumerable tcpConnections = ipProps.GetActiveTcpConnections() .Where(c => c.State == TcpState.Listen) diff --git a/Source/Bookgen.Lib/ImageService/CachedImageService.cs b/Source/Bookgen.Lib/ImageService/CachedImageService.cs index 19330d8e..085192e6 100644 --- a/Source/Bookgen.Lib/ImageService/CachedImageService.cs +++ b/Source/Bookgen.Lib/ImageService/CachedImageService.cs @@ -22,10 +22,10 @@ public CachedImageService(IImgService service) public ImageResult GetImageEmbedData(string path) { - if (_cache.TryGetValue(path, out var data)) + if (_cache.TryGetValue(path, out ImageResult? data)) return data; - var result = _service.GetImageEmbedData(path); + ImageResult result = _service.GetImageEmbedData(path); _cache.TryAdd(path, result); return result; } diff --git a/Source/Bookgen.Lib/ImageService/ImageConverter.cs b/Source/Bookgen.Lib/ImageService/ImageConverter.cs index d53f4240..6a8f6d41 100644 --- a/Source/Bookgen.Lib/ImageService/ImageConverter.cs +++ b/Source/Bookgen.Lib/ImageService/ImageConverter.cs @@ -13,8 +13,8 @@ public static class ImageConverter { public static void Encode(string source, string output, ImageType imageType, int width, int height, int quality) { - using var srcStream = File.OpenRead(source); - using var destStream = File.Create(output); + using FileStream srcStream = File.OpenRead(source); + using FileStream destStream = File.Create(output); if (Path.GetExtension("soruce").Equals(".svg", StringComparison.OrdinalIgnoreCase)) { diff --git a/Source/Bookgen.Lib/ImageService/ImgService.cs b/Source/Bookgen.Lib/ImageService/ImgService.cs index 56ba90e7..e16ef2e4 100644 --- a/Source/Bookgen.Lib/ImageService/ImgService.cs +++ b/Source/Bookgen.Lib/ImageService/ImgService.cs @@ -115,7 +115,7 @@ static ImageType GetImateType(SvgRecodeOption recodeOption) _imageConfig.ImageQualityOnResize, _imageConfig.ResizeAndRecodeImages); - var type = _imageConfig.ResizeAndRecodeImages switch + ImageType type = _imageConfig.ResizeAndRecodeImages switch { ImgRecodeOption.AsPng => ImageType.Png, ImgRecodeOption.AsWebp => ImageType.Webp, diff --git a/Source/Bookgen.Lib/ImageService/Utils.cs b/Source/Bookgen.Lib/ImageService/Utils.cs index 2c0cc361..b4600762 100644 --- a/Source/Bookgen.Lib/ImageService/Utils.cs +++ b/Source/Bookgen.Lib/ImageService/Utils.cs @@ -21,7 +21,7 @@ public static byte[] ConvertToPng(string file, int maxwidth, int maxHeight) { if (Path.GetExtension(file).Equals(".svg", StringComparison.OrdinalIgnoreCase)) { - using var stream = File.OpenRead(file); + using FileStream stream = File.OpenRead(file); return RenderSvg(stream, maxwidth, maxHeight, SvgRecodeOption.AsPng).ToArray(); } using SKBitmap bitmap = SKBitmap.Decode(file); diff --git a/Source/Bookgen.Lib/Internals/Extensions.cs b/Source/Bookgen.Lib/Internals/Extensions.cs index 94c1ecad..f95d6046 100644 --- a/Source/Bookgen.Lib/Internals/Extensions.cs +++ b/Source/Bookgen.Lib/Internals/Extensions.cs @@ -41,7 +41,7 @@ static FrontMatter CreateDefaultFrontMatter(string diskPath, ILogger log) StringBuilder content = new StringBuilder(); StringBuilder yaml = new StringBuilder(); - using var reader = folder.OpenTextReader(file); + using TextReader reader = folder.OpenTextReader(file); string? line; bool inYaml = false; @@ -98,11 +98,11 @@ public async Task GetSourceFile(string file, ILogger logger) public async Task GetCoverFileName(TableOfContents tableOfContents, ILogger logger) { var contents = await folder.ReadAllTextAsync(tableOfContents.IndexFile); - foreach (var block in Markdig.Markdown.Parse(contents)) + foreach (Block block in Markdig.Markdown.Parse(contents)) { if (block is ParagraphBlock paragraph && paragraph.Inline != null) { - foreach (var inline in paragraph.Inline) + foreach (Inline inline in paragraph.Inline) { if (inline is LinkInline link && link.IsImage) { diff --git a/Source/Bookgen.Lib/Internals/SerializedObjectValidator.cs b/Source/Bookgen.Lib/Internals/SerializedObjectValidator.cs index f8bb42de..aba275d0 100644 --- a/Source/Bookgen.Lib/Internals/SerializedObjectValidator.cs +++ b/Source/Bookgen.Lib/Internals/SerializedObjectValidator.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using System.ComponentModel.DataAnnotations; +using System.Reflection; using System.Security.AccessControl; using BookGen.Vfs; @@ -44,7 +45,7 @@ public bool Validate(T @object, ICollection issues) where T : class { static void AddIssues(ICollection target, string prefix, IEnumerable results) { - foreach (var validationResult in results) + foreach (ValidationResult validationResult in results) { var names = string.Join(',', validationResult.MemberNames); @@ -74,7 +75,7 @@ static void AddIssues(ICollection target, string prefix, IEnumerable properties = @object .GetType() .GetProperties() .Where(p => p.CanRead @@ -84,7 +85,7 @@ static void AddIssues(ICollection target, string prefix, IEnumerable htmlRenderer) { - var linkInLineRenderer = htmlRenderer.ObjectRenderers.FindExact(); + LinkInlineRenderer? linkInLineRenderer = htmlRenderer.ObjectRenderers.FindExact(); if (linkInLineRenderer != null) { htmlRenderer.ObjectRenderers.Remove(linkInLineRenderer); diff --git a/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs b/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs index 7e42a716..e51abc2e 100644 --- a/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs +++ b/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs @@ -8,6 +8,7 @@ using Markdig; using Markdig.Parsers; +using Markdig.Syntax; namespace Bookgen.Lib.Markdown; @@ -18,14 +19,14 @@ public sealed class MarkdownConverter : IDisposable public MarkdownConverter(RenderSettings settings) { - var configuration = new MarkdownPipelineBuilder() + MarkdownPipelineBuilder configuration = new MarkdownPipelineBuilder() .UseAdvancedExtensions() .UseTableOfContents() .UseMathematics() .UseYamlFrontMatter() .Use(); - foreach (var extension in configuration.Extensions) + foreach (IMarkdownExtension extension in configuration.Extensions) { if (extension is BookGenExtension bookGenExtension) { @@ -57,7 +58,7 @@ public string RenderMarkdownToHtml(string markdown) public string RenderMarkdownToTerminal(string markdown, RenderOptions? renderOptions = null) { - var document = MarkdownParser.Parse(markdown, _terminalPipeLine); + MarkdownDocument document = MarkdownParser.Parse(markdown, _terminalPipeLine); using var writer = new StringWriter(); diff --git a/Source/Bookgen.Lib/Markdown/RenderSettings.cs b/Source/Bookgen.Lib/Markdown/RenderSettings.cs index 3a4f9a43..21dba241 100644 --- a/Source/Bookgen.Lib/Markdown/RenderSettings.cs +++ b/Source/Bookgen.Lib/Markdown/RenderSettings.cs @@ -23,7 +23,7 @@ public sealed class RenderSettings : IDisposable public string RequestImage(string url) { - var img = _imgService.GetImageEmbedData(url); + ImageResult img = _imgService.GetImageEmbedData(url); return ImageUrlRewriter(img); } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/ExtendedLinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/ExtendedLinkInlineRenderer.cs index 1337af1f..76dcc238 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/ExtendedLinkInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/ExtendedLinkInlineRenderer.cs @@ -3,6 +3,7 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using Markdig.Renderers; @@ -65,7 +66,7 @@ private static bool TryGetQueryParam(Uri uri, string queryparam, [NotNullWhen(tr value = null; return false; } - var queryParams = System.Web.HttpUtility.ParseQueryString(query); + NameValueCollection queryParams = System.Web.HttpUtility.ParseQueryString(query); value = queryParams[queryparam]; return value != null; diff --git a/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs index ea4dbbf0..587f458e 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs @@ -8,6 +8,7 @@ using Bookgen.Lib.JsInterop; +using Markdig.Helpers; using Markdig.Parsers; using Markdig.Renderers; using Markdig.Renderers.Html; @@ -68,8 +69,8 @@ public static string GetCode(LeafBlock node) int totalLines = lines.Length; for (int i = 0; i < totalLines; i++) { - var line = lines[i]; - var slice = line.Slice; + StringLine line = lines[i]; + StringSlice slice = line.Slice; if (slice.Text == null) { continue; diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs index 842fe39e..f5452f56 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs @@ -2,6 +2,8 @@ using Markdig.Syntax.Inlines; +using Webmaster442.WindowsTerminal; + namespace Bookgen.Lib.Markdown.Renderers.Terminal; internal sealed class EmphasisInlineRenderer : TerminalObjectRenderer @@ -32,7 +34,7 @@ protected override void Write(TerminalRenderer renderer, EmphasisInline obj) return; } - var preformat = renderer.Builder.New(); + TerminalFormattedStringBuilder preformat = renderer.Builder.New(); if (option == RenderAs.Bold) preformat.WithBold(); diff --git a/Source/Bookgen.Lib/Markdown/TableOfContents/CustomAutoIdExtension.cs b/Source/Bookgen.Lib/Markdown/TableOfContents/CustomAutoIdExtension.cs index 0ccb1632..b601176f 100644 --- a/Source/Bookgen.Lib/Markdown/TableOfContents/CustomAutoIdExtension.cs +++ b/Source/Bookgen.Lib/Markdown/TableOfContents/CustomAutoIdExtension.cs @@ -3,6 +3,8 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using System.Text; + using ExCSS; using Markdig; @@ -39,14 +41,14 @@ public CustomAutoIdExtension(CustomAutoIdOptions options) public void Setup(MarkdownPipelineBuilder pipeline) { - var headingBlockParser = pipeline.BlockParsers.Find(); + HeadingBlockParser? headingBlockParser = pipeline.BlockParsers.Find(); if (headingBlockParser is not null) { // Install a hook on the HeadingBlockParser when a HeadingBlock is actually processed headingBlockParser.Closed -= HeadingBlockParser_Closed; headingBlockParser.Closed += HeadingBlockParser_Closed; } - var paragraphBlockParser = pipeline.BlockParsers.FindExact(); + ParagraphBlockParser? paragraphBlockParser = pipeline.BlockParsers.FindExact(); if (paragraphBlockParser is not null) { // Install a hook on the ParagraphBlockParser when a HeadingBlock is actually processed as a Setex heading @@ -75,7 +77,7 @@ private void HeadingBlockParser_Closed(BlockProcessor processor, Block block) // If the AutoLink options is set, we register a LinkReferenceDefinition at the document level if (options.HeadingIdGenerator == null && (options.Options & AutoIdentifierOptions.AutoLink) != 0) { - var headingLine = headingBlock.Lines.Lines[0]; + StringLine headingLine = headingBlock.Lines.Lines[0]; var text = headingLine.ToString(); @@ -84,7 +86,7 @@ private void HeadingBlockParser_Closed(BlockProcessor processor, Block block) CreateLinkInline = CreateLinkInlineForHeading }; - var doc = processor.Document; + MarkdownDocument doc = processor.Document; var dictionary = doc.GetData(this) as Dictionary; if (dictionary is null) { @@ -101,15 +103,15 @@ private void HeadingBlockParser_Closed(BlockProcessor processor, Block block) private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? inline) { - var doc = processor.Document; + MarkdownDocument doc = processor.Document; doc.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin; var dictionary = (Dictionary)doc.GetData(this)!; - foreach (var keyPair in dictionary) + foreach (KeyValuePair keyPair in dictionary) { // Here we make sure that auto-identifiers will not override an existing link definition // defined in the document // If it is the case, we skip the auto identifier for the Heading - if (!doc.TryGetLinkReferenceDefinition(keyPair.Key, out var linkDef)) + if (!doc.TryGetLinkReferenceDefinition(keyPair.Key, out LinkReferenceDefinition? linkDef)) { doc.SetLinkReferenceDefinition(keyPair.Key, keyPair.Value, true); } @@ -155,13 +157,13 @@ private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline? i } // Use internally a HtmlRenderer to strip links from a heading - var stripRenderer = rendererCache.Get(); + CacheHtmlRenderer stripRenderer = rendererCache.Get(); stripRenderer.Render(headingBlock.Inline); var headingText = stripRenderer.Writer.ToString()!; rendererCache.Release(stripRenderer); // If id is already set, don't try to modify it - var attributes = processor.Block!.GetAttributes(); + HtmlAttributes attributes = processor.Block!.GetAttributes(); try { if (options.HeadingIdGenerator is not null) @@ -184,7 +186,7 @@ private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline? i // Add a trailing -1, -2, -3...etc. in case of collision int index = 0; var headingId = baseHeadingId; - var headingBuffer = StringBuilderCache.Local(); + StringBuilder headingBuffer = StringBuilderCache.Local(); while (!identifiers.Add(headingId)) { index++; diff --git a/Source/Bookgen.Lib/Markdown/TableOfContents/HeadingInfos.cs b/Source/Bookgen.Lib/Markdown/TableOfContents/HeadingInfos.cs index afe46da2..e25b6060 100644 --- a/Source/Bookgen.Lib/Markdown/TableOfContents/HeadingInfos.cs +++ b/Source/Bookgen.Lib/Markdown/TableOfContents/HeadingInfos.cs @@ -34,7 +34,7 @@ void RenderHtmlLint(HtmlRenderer renderer, TocState options) renderer.WriteLine("
    "); - foreach (var item in Children) + foreach (HeadingInfos item in Children) { if (item.Level > options.MaxLevel) continue; diff --git a/Source/Bookgen.Lib/Markdown/TableOfContents/HtmlTocRenderer.cs b/Source/Bookgen.Lib/Markdown/TableOfContents/HtmlTocRenderer.cs index ed6dc58e..f6170921 100644 --- a/Source/Bookgen.Lib/Markdown/TableOfContents/HtmlTocRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/TableOfContents/HtmlTocRenderer.cs @@ -23,7 +23,7 @@ protected override void Write(HtmlRenderer renderer, TocBlock obj) return; renderer.EnsureLine(); - var attr = obj.GetAttributes(); + HtmlAttributes attr = obj.GetAttributes(); if (attr.Id is null) attr.Id = "toc"; diff --git a/Source/Bookgen.Lib/Markdown/TableOfContents/LevelList.cs b/Source/Bookgen.Lib/Markdown/TableOfContents/LevelList.cs index 97d9cf29..23b5f76f 100644 --- a/Source/Bookgen.Lib/Markdown/TableOfContents/LevelList.cs +++ b/Source/Bookgen.Lib/Markdown/TableOfContents/LevelList.cs @@ -104,7 +104,7 @@ public void Append(T item) if (item.Level > _current.Level) { //try to find last child that child.Level > item.Level - var found = LevelList.FindChildLevelLessThan(item.Level, _current); + T found = LevelList.FindChildLevelLessThan(item.Level, _current); var offset = item.Level - found.Level; if (offset > 1) { @@ -131,7 +131,7 @@ public void Append(T item) } //not find a right item, move to previous one - var parent = LevelList.FindParentLevelLessThan(item.Level, _current); + T? parent = LevelList.FindParentLevelLessThan(item.Level, _current); if (parent is not null) { //marge siblings which @@ -159,7 +159,7 @@ protected virtual void MargeSiblings(T parent, T add) for (int i = 0; i < parent.Count; i++) { - var t = parent[i]; + T t = parent[i]; if (t.Level > add.Level) { if (startAt == -1) @@ -193,7 +193,7 @@ protected virtual void MargeSiblings(T parent, T add) }; for (int k = start; k < end; k++) { - var item = parent[k]; + T item = parent[k]; item.Parent = emtpy; emtpy._data.Add(item); if (start == end - 1) diff --git a/Source/Bookgen.Lib/Markdown/TableOfContents/TocBlockParser.cs b/Source/Bookgen.Lib/Markdown/TableOfContents/TocBlockParser.cs index b76f4e79..fd7df598 100644 --- a/Source/Bookgen.Lib/Markdown/TableOfContents/TocBlockParser.cs +++ b/Source/Bookgen.Lib/Markdown/TableOfContents/TocBlockParser.cs @@ -39,14 +39,14 @@ public override BlockState TryOpen(BlockProcessor processor) int column = processor.Column; int sourcePosition = line.Start; - var matches = TocTagMatcher().Matches(line.ToString()); + MatchCollection matches = TocTagMatcher().Matches(line.ToString()); if (matches.Count < 1) return BlockState.None; int tagLength = matches.Select(x => x.Value.Length).Sum(); - var maxLevelMathes = MaxLevelMatcher().Matches(line.ToString()); + MatchCollection maxLevelMathes = MaxLevelMatcher().Matches(line.ToString()); if (maxLevelMathes.Count > 0) { if (int.TryParse(maxLevelMathes[0].Groups.Values.Last().Value, out int maxLevel)) diff --git a/Source/Bookgen.Lib/Markdown/TableOfContents/TocExtension.cs b/Source/Bookgen.Lib/Markdown/TableOfContents/TocExtension.cs index 427751be..c9621407 100644 --- a/Source/Bookgen.Lib/Markdown/TableOfContents/TocExtension.cs +++ b/Source/Bookgen.Lib/Markdown/TableOfContents/TocExtension.cs @@ -36,7 +36,7 @@ public TocExtension(TocState state) //register parsers public void Setup(MarkdownPipelineBuilder pipeline) { - var autoIdExtension = pipeline.Extensions.Find(); + CustomAutoIdExtension? autoIdExtension = pipeline.Extensions.Find(); if (autoIdExtension == null) throw new InvalidOperationException("CustomAutoIdExtension is null"); diff --git a/Source/Bookgen.Lib/Pipeline/Epub/CreateContentOpf.cs b/Source/Bookgen.Lib/Pipeline/Epub/CreateContentOpf.cs index 5203ff47..60d7d100 100644 --- a/Source/Bookgen.Lib/Pipeline/Epub/CreateContentOpf.cs +++ b/Source/Bookgen.Lib/Pipeline/Epub/CreateContentOpf.cs @@ -75,7 +75,7 @@ public override Task ExecuteAsync(IBookEnvironment environment, ILog } }; - var coverItem = State.PackageItems.FirstOrDefault(item => item.Properties == "cover-image"); + PackageItem? coverItem = State.PackageItems.FirstOrDefault(item => item.Properties == "cover-image"); if (coverItem != null) { opf.Metadata.Meta.Add(new PackageMetadataMeta diff --git a/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs b/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs index 51049ee7..dd497091 100644 --- a/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs @@ -7,6 +7,7 @@ using Bookgen.Lib.Domain; using Bookgen.Lib.Domain.Epub; +using Bookgen.Lib.Domain.IO; using Bookgen.Lib.Domain.IO.Configuration; using Bookgen.Lib.ImageService; using Bookgen.Lib.Internals; @@ -69,7 +70,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment await RenderIndex(environment, logger, markdown, renderer, template); - foreach (var chapter in environment.TableOfContents.Chapters) + foreach (TocChapter chapter in environment.TableOfContents.Chapters) { logger.LogInformation("Rendering chapter {chapter}...", chapter.Title); fileId = 1; @@ -116,7 +117,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment private async Task RenderIndex(IBookEnvironment environment, ILogger logger, MarkdownConverter markdown, TemplateEngine renderer, string template) { - var index = await environment.Source.GetSourceFile(environment.TableOfContents.IndexFile, logger); + SourceFile index = await environment.Source.GetSourceFile(environment.TableOfContents.IndexFile, logger); var indexView = new ViewData { diff --git a/Source/Bookgen.Lib/Pipeline/Epub/CreateImageFiles.cs b/Source/Bookgen.Lib/Pipeline/Epub/CreateImageFiles.cs index 1f69d73e..688321b4 100644 --- a/Source/Bookgen.Lib/Pipeline/Epub/CreateImageFiles.cs +++ b/Source/Bookgen.Lib/Pipeline/Epub/CreateImageFiles.cs @@ -21,7 +21,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment { logger.LogInformation("Writing {count} images to epub...", State.ImagesData.Count); - foreach (var image in State.ImagesData) + foreach (KeyValuePair image in State.ImagesData) { logger.LogDebug("Writing {image}...", image.Key); await State.EpubFile.AddAsync($"EPUB/content/{image.Key}", Convert.FromBase64String(image.Value)); diff --git a/Source/Bookgen.Lib/Pipeline/Epub/CreateNav.cs b/Source/Bookgen.Lib/Pipeline/Epub/CreateNav.cs index db19b19d..dfe8814b 100644 --- a/Source/Bookgen.Lib/Pipeline/Epub/CreateNav.cs +++ b/Source/Bookgen.Lib/Pipeline/Epub/CreateNav.cs @@ -21,17 +21,17 @@ public CreateNav(EpubState state) : base(state) public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { - var ncx = CreateNcx(environment); + Ncx ncx = CreateNcx(environment); var tocHtml = new EpubTocRenderer(); tocHtml.BeginNav(); tocHtml.AddTitle(environment.Configuration.BookTitle); tocHtml.BeginOl(display: false); - foreach (var chapter in State.TocData) + foreach (KeyValuePair> chapter in State.TocData) { tocHtml.BeginChapter(chapter.Key); tocHtml.BeginOl(); - foreach (var item in chapter.Value) + foreach (EpubState.ChapterItem item in chapter.Value) { tocHtml.AddItem(item.Title, item.FileName.Replace("content/", "")); string id = IdGenerator.Generate32BitDeterministicId(item.FileName); diff --git a/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs b/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs index 9d30d96a..dfa0f799 100644 --- a/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs +++ b/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs @@ -6,6 +6,7 @@ using System.ServiceModel.Syndication; using Bookgen.Lib.Domain; +using Bookgen.Lib.Domain.IO; using Bookgen.Lib.ImageService; using Bookgen.Lib.Internals; using Bookgen.Lib.JsInterop; @@ -43,7 +44,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment List items = new(); - foreach (var chapter in environment.TableOfContents.Chapters) + foreach (TocChapter chapter in environment.TableOfContents.Chapters) { logger.LogInformation("Rendering chapter {chapter}...", chapter.Title); diff --git a/Source/Bookgen.Lib/Pipeline/Feed/WriteFeeds.cs b/Source/Bookgen.Lib/Pipeline/Feed/WriteFeeds.cs index 5bccd77b..72f4b520 100644 --- a/Source/Bookgen.Lib/Pipeline/Feed/WriteFeeds.cs +++ b/Source/Bookgen.Lib/Pipeline/Feed/WriteFeeds.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using System.ServiceModel.Syndication; +using System.Xml; using BookGen.Vfs; @@ -20,14 +21,14 @@ public WriteFeeds(SyndicationFeedState state) : base(state) public override Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { logger.LogInformation("Writing RSS feed..."); - using (var rss = environment.Output.CreateXmlWriter("rss.xml")) + using (XmlWriter rss = environment.Output.CreateXmlWriter("rss.xml")) { Rss20FeedFormatter rssFormatter = new Rss20FeedFormatter(State.Feed); rssFormatter.WriteTo(rss); } logger.LogInformation("Writing Atom feed..."); - using (var atom = environment.Output.CreateXmlWriter("atom.xml")) + using (XmlWriter atom = environment.Output.CreateXmlWriter("atom.xml")) { Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter(State.Feed); atomFormatter.WriteTo(atom); diff --git a/Source/Bookgen.Lib/Pipeline/Pipeline.cs b/Source/Bookgen.Lib/Pipeline/Pipeline.cs index af578c0f..53a96768 100644 --- a/Source/Bookgen.Lib/Pipeline/Pipeline.cs +++ b/Source/Bookgen.Lib/Pipeline/Pipeline.cs @@ -25,7 +25,7 @@ public Pipeline(params IPipeLineStep[] steps) public async Task ExecuteAsync(IBookEnvironment environment, ILogger logger, CancellationToken cancellationToken) { - foreach (var step in Steps) + foreach (IPipeLineStep step in Steps) { if (cancellationToken.IsCancellationRequested) { @@ -33,7 +33,7 @@ public async Task ExecuteAsync(IBookEnvironment environment, ILogger logge return false; } - var result = await step.ExecuteAsync(environment, logger); + StepResult result = await step.ExecuteAsync(environment, logger); if (result == StepResult.Failure) { return false; diff --git a/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs b/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs index 78bea26a..e8276fe9 100644 --- a/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs +++ b/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using Bookgen.Lib.Domain; +using Bookgen.Lib.Domain.IO; using Bookgen.Lib.Domain.IO.Configuration; using Bookgen.Lib.Domain.PostProcess; using Bookgen.Lib.ImageService; @@ -46,7 +47,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment Chapters = new List() }; - foreach (var chapter in environment.TableOfContents.Chapters) + foreach (TocChapter chapter in environment.TableOfContents.Chapters) { ExportChapter exportChapter = new ExportChapter { diff --git a/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs b/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs index 91a6277f..51fe6fa3 100644 --- a/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using Bookgen.Lib.Domain; +using Bookgen.Lib.Domain.IO; using Bookgen.Lib.ImageService; using Bookgen.Lib.Internals; using Bookgen.Lib.JsInterop; @@ -37,7 +38,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment using var markdown = new MarkdownConverter(settings); - foreach (var chapter in environment.TableOfContents.Chapters) + foreach (TocChapter chapter in environment.TableOfContents.Chapters) { logger.LogInformation("Rendering chapter {chapter}...", chapter.Title); State.Buffer.AppendH1(chapter.Title); diff --git a/Source/Bookgen.Lib/Pipeline/Print/WriteHtml.cs b/Source/Bookgen.Lib/Pipeline/Print/WriteHtml.cs index 8d22c624..f15c2747 100644 --- a/Source/Bookgen.Lib/Pipeline/Print/WriteHtml.cs +++ b/Source/Bookgen.Lib/Pipeline/Print/WriteHtml.cs @@ -34,7 +34,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment AdditionalData = new(), }; - using var writer = environment.Output.CreateTextWriter("print.html"); + using TextWriter writer = environment.Output.CreateTextWriter("print.html"); renderer.Render(writer, tempate, viewData); return StepResult.Success; diff --git a/Source/Bookgen.Lib/Pipeline/Print/WriteXHtml.cs b/Source/Bookgen.Lib/Pipeline/Print/WriteXHtml.cs index 93b38f91..d3ba5c37 100644 --- a/Source/Bookgen.Lib/Pipeline/Print/WriteXHtml.cs +++ b/Source/Bookgen.Lib/Pipeline/Print/WriteXHtml.cs @@ -68,7 +68,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment AdditionalData = new(), }; - using var writer = environment.Output.CreateTextWriter("print.xhtml.html"); + using TextWriter writer = environment.Output.CreateTextWriter("print.xhtml.html"); renderer.Render(writer, tempate, viewData); return StepResult.Success; diff --git a/Source/Bookgen.Lib/Pipeline/StaticWebsite/CreateEmptyIndexPagesForFolders.cs b/Source/Bookgen.Lib/Pipeline/StaticWebsite/CreateEmptyIndexPagesForFolders.cs index bbf8c329..6dd62480 100644 --- a/Source/Bookgen.Lib/Pipeline/StaticWebsite/CreateEmptyIndexPagesForFolders.cs +++ b/Source/Bookgen.Lib/Pipeline/StaticWebsite/CreateEmptyIndexPagesForFolders.cs @@ -15,7 +15,7 @@ public CreateEmptyIndexPagesForFolders(StaticWebState staticWebState) : base(sta public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { - var folders = environment.Output.GetDirectories(environment.Output.Scope, true); + IEnumerable folders = environment.Output.GetDirectories(environment.Output.Scope, true); var protect = environment.GetAsset(BundledAssets.ProtectHtml); diff --git a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderTableOfContents.cs b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderTableOfContents.cs index f220c99d..213de692 100644 --- a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderTableOfContents.cs +++ b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderTableOfContents.cs @@ -26,7 +26,7 @@ public override Task ExecuteAsync(IBookEnvironment environment, ILog { TocRenderer toc = new(environment.Configuration.StaticWebsiteConfig.TocConfiguration); toc.BeginContainer(); - foreach (var chapter in environment.TableOfContents.Chapters) + foreach (TocChapter chapter in environment.TableOfContents.Chapters) { toc.BeginChapter(chapter.Title); toc.BeginOuterItemContainer(); diff --git a/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs b/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs index cf922e99..f84c257b 100644 --- a/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs @@ -7,6 +7,8 @@ using System.Globalization; using System.Text; +using Bookgen.Lib.Domain; +using Bookgen.Lib.Domain.IO; using Bookgen.Lib.Domain.Wordpress; using Bookgen.Lib.ImageService; using Bookgen.Lib.Internals; @@ -148,7 +150,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment globalparent = uid; ++uid; - foreach (var chapter in environment.TableOfContents.Chapters) + foreach (TocChapter chapter in environment.TableOfContents.Chapters) { string chapterPath = $"{environment.Configuration.StaticWebsiteConfig.DeployHost}{EncodeTitle(chapter.Title)}"; int parent_uid = uid; @@ -169,7 +171,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment { logger.LogDebug("Processing file {File}...", file); - var sourceData = await environment.Source.GetSourceFile(file, logger); + SourceFile sourceData = await environment.Source.GetSourceFile(file, logger); string subpath = $"{environment.Configuration.WordpressConfig.DeployHost}{EncodeTitle(chapter.Title)}/{EncodeTitle(sourceData.FrontMatter.Title)}"; string template = await environment.GetTemplate(frontMatterTemplate: sourceData.FrontMatter.Template, diff --git a/Source/Bookgen.Lib/Pipeline/Wordpress/WriteExportFile.cs b/Source/Bookgen.Lib/Pipeline/Wordpress/WriteExportFile.cs index aeabc1f0..6671be78 100644 --- a/Source/Bookgen.Lib/Pipeline/Wordpress/WriteExportFile.cs +++ b/Source/Bookgen.Lib/Pipeline/Wordpress/WriteExportFile.cs @@ -35,7 +35,7 @@ public override Task ExecuteAsync(IBookEnvironment environment, ILog xnames.Add("wp", "http://wordpress.org/export/1.2/"); var xs = new XmlSerializer(typeof(Rss)); - using var fileStream = environment.Output.CreateWriteStream("wodpress-export.xml"); + using Stream fileStream = environment.Output.CreateWriteStream("wodpress-export.xml"); xs.Serialize(fileStream, output, xnames); diff --git a/Source/Bookgen.Lib/Templates/Functions.cs b/Source/Bookgen.Lib/Templates/Functions.cs index 8f4e35e9..7634b7c0 100644 --- a/Source/Bookgen.Lib/Templates/Functions.cs +++ b/Source/Bookgen.Lib/Templates/Functions.cs @@ -21,7 +21,7 @@ public static TValue GetValueOrDefault(this IReadOnlyList argume return defaultValue; var value = arguments[index]; - if (TValue.TryParse(value, CultureInfo.InvariantCulture, out var parsedValue)) + if (TValue.TryParse(value, CultureInfo.InvariantCulture, out TValue? parsedValue)) return parsedValue; return defaultValue; diff --git a/Source/Bookgen.Lib/Templates/TemplateEngine.cs b/Source/Bookgen.Lib/Templates/TemplateEngine.cs index a69d1b32..261777ea 100644 --- a/Source/Bookgen.Lib/Templates/TemplateEngine.cs +++ b/Source/Bookgen.Lib/Templates/TemplateEngine.cs @@ -51,7 +51,7 @@ public string Render(string template, TData viewData) where TData : ViewD public void Render(TextWriter target, string template, TData viewData) where TData : ViewData { - var dataTable = viewData.GetDataTable(_comparer); + Dictionary dataTable = viewData.GetDataTable(_comparer); StringBuilder lineBuffer = new(120); @@ -60,7 +60,7 @@ public void Render(TextWriter target, string template, TData viewData) wh while ((line = reader.ReadLine()) != null) { - var templatePartsInLine = TemplatePartRegex().Matches(line); + MatchCollection templatePartsInLine = TemplatePartRegex().Matches(line); if (templatePartsInLine.Count < 1) { @@ -80,7 +80,7 @@ public void Render(TextWriter target, string template, TData viewData) wh string functionName = templateFunction.Skip(1).First(); string[] arguments = templateFunction.Skip(2).TakeWhile(f => f != "}}").ToArray(); - if (!_lambdaTable.TryGetValue(functionName, out var function)) + if (!_lambdaTable.TryGetValue(functionName, out Func? function)) { _logger.LogWarning("Function {FunctionName} is not registered.", functionName); lineBuffer.Append($"Function {functionName} is not registered."); diff --git a/Source/Bookgen.Lib/Templates/ViewData.cs b/Source/Bookgen.Lib/Templates/ViewData.cs index 597d2c7a..4556cc9e 100644 --- a/Source/Bookgen.Lib/Templates/ViewData.cs +++ b/Source/Bookgen.Lib/Templates/ViewData.cs @@ -34,16 +34,16 @@ public class ViewData public Dictionary GetDataTable(StringComparer comparer) { Dictionary result = new(comparer); - var properties = GetType() + IEnumerable properties = GetType() .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.Name != nameof(AdditionalData)); - foreach (var property in properties) + foreach (PropertyInfo? property in properties) { result.Add(property.Name, property.GetValue(this)?.ToString() ?? ""); } - foreach (var kvp in AdditionalData) + foreach (KeyValuePair kvp in AdditionalData) { result.Add(kvp.Key, kvp.Value); } diff --git a/Test/Bookgen.Tests/EmbeddedTestFolder.cs b/Test/Bookgen.Tests/EmbeddedTestFolder.cs index d342d021..fd34bc20 100644 --- a/Test/Bookgen.Tests/EmbeddedTestFolder.cs +++ b/Test/Bookgen.Tests/EmbeddedTestFolder.cs @@ -52,20 +52,20 @@ public Stream OpenReadStream(string path) public TextReader OpenTextReader(string path) { - using var stream = OpenReadStream(path); + using Stream stream = OpenReadStream(path); return new StreamReader(stream); } public string ReadAllText(string path) { - using var stream = OpenReadStream(path); + using Stream stream = OpenReadStream(path); using var reader = new StreamReader(stream); return reader.ReadToEnd(); } public async Task ReadAllTextAsync(string path) { - using var stream = OpenReadStream(path); + using Stream stream = OpenReadStream(path); using var reader = new StreamReader(stream); return await reader.ReadToEndAsync(); } diff --git a/Test/Bookgen.Tests/Lib/UT_ImgService.cs b/Test/Bookgen.Tests/Lib/UT_ImgService.cs index ca5e4509..bd170f36 100644 --- a/Test/Bookgen.Tests/Lib/UT_ImgService.cs +++ b/Test/Bookgen.Tests/Lib/UT_ImgService.cs @@ -34,7 +34,7 @@ public void EnsureThat_SvgPassThroughReturnsExpected() SvgRecode = SvgRecodeOption.Passtrough, }); - var result = service.GetImageEmbedData("test.svg"); + ImageResult result = service.GetImageEmbedData("test.svg"); string expected = """ @@ -129,7 +129,7 @@ public void EnsureThat_Svg_Recode_Webp_ReturnsExpected() SvgRecode = SvgRecodeOption.AsWebp, }); - var result = service.GetImageEmbedData("test.svg"); + ImageResult result = service.GetImageEmbedData("test.svg"); Assert.Multiple(() => @@ -151,7 +151,7 @@ public void EnsureThat_Svg_Recode_Resize_Png_ReturnsExpected() ResizeHeight = 200, }); - var result = service.GetImageEmbedData("test.svg"); + ImageResult result = service.GetImageEmbedData("test.svg"); Assert.Multiple(() => @@ -167,7 +167,7 @@ public void EnsureThat_Png_Passtrough() { var service = new ImgService(_testFolder, _mockLogger.Object, new ImageConfig()); - var result = service.GetImageEmbedData("test.png"); + ImageResult result = service.GetImageEmbedData("test.png"); Assert.Multiple(() => { @@ -188,7 +188,7 @@ public void EnsureThat_Png_Recode_Webp_Works() ImageQualityOnResize = 80, }); - var result = service.GetImageEmbedData("test.png"); + ImageResult result = service.GetImageEmbedData("test.png"); Assert.Multiple(() => { diff --git a/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs b/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs index 6c7ad7ce..a9c45f29 100644 --- a/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs +++ b/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs @@ -63,7 +63,7 @@ public void EnsureThat_Merge_Works() JsonMerger sut = new JsonMerger(node1!); sut.Merge(nodeOverlay!); - var result = sut.Deserialize(); + Config? result = sut.Deserialize(); Assert.That(result, Is.Not.Null); diff --git a/Test/Bookgen.Tests/Lib/UT_LevelList.cs b/Test/Bookgen.Tests/Lib/UT_LevelList.cs index a1aa8b22..80efd6c3 100644 --- a/Test/Bookgen.Tests/Lib/UT_LevelList.cs +++ b/Test/Bookgen.Tests/Lib/UT_LevelList.cs @@ -27,7 +27,7 @@ public string ToFormatedString(string prefix = "", StringBuilder? sb = null) sb.AppendLine(); } prefix += " "; - foreach (var item in Children) + foreach (TestLevelList item in Children) { item.ToFormatedString(prefix, sb); } diff --git a/Test/Bookgen.Tests/Shell.Shared/UT_GitParser.cs b/Test/Bookgen.Tests/Shell.Shared/UT_GitParser.cs index cf994285..7497caf4 100644 --- a/Test/Bookgen.Tests/Shell.Shared/UT_GitParser.cs +++ b/Test/Bookgen.Tests/Shell.Shared/UT_GitParser.cs @@ -87,7 +87,7 @@ public void EnsureThat_ParseBranches_Works() remotes/origin/HEAD -> origin/master """; - var result = GitParser.ParseBranches(input); + HashSet result = GitParser.ParseBranches(input); Assert.Multiple(() => { Assert.That(result, Has.Count.EqualTo(2)); diff --git a/Test/Bookgen.Tests/Shell.Shared/UT_ShellAutoCompleteFilter.cs b/Test/Bookgen.Tests/Shell.Shared/UT_ShellAutoCompleteFilter.cs index edb72d11..99043447 100644 --- a/Test/Bookgen.Tests/Shell.Shared/UT_ShellAutoCompleteFilter.cs +++ b/Test/Bookgen.Tests/Shell.Shared/UT_ShellAutoCompleteFilter.cs @@ -13,7 +13,7 @@ public class UT_ShellAutoCompleteFilter [Test] public void EnsureThat_ShellAutoCompleteFilter_DoFilter_ReturnsGood() { - var results = ShellAutoCompleteFilter.DoFilter(["git add", "git add ."], "git a", 5); + IEnumerable results = ShellAutoCompleteFilter.DoFilter(["git add", "git add ."], "git a", 5); Assert.That(results, Is.EqualTo(new[] { "add", "add ." }).AsCollection); } @@ -25,14 +25,14 @@ public void EnsureThat_ShellAutoCompleteFilter_DoFilter_ReturnsGood_Mutiple_Posi [ "git merge master", ]; - var results = ShellAutoCompleteFilter.DoFilter(data, input, position); + IEnumerable results = ShellAutoCompleteFilter.DoFilter(data, input, position); Assert.That(results, Is.EqualTo(new[] { expected }).AsCollection); } [Test] public void EnsureThat_GetWordPositions_Works() { - var results = ShellAutoCompleteFilter.GetWordPositions("git merge master").ToArray(); + (int start, int end)[] results = ShellAutoCompleteFilter.GetWordPositions("git merge master").ToArray(); Assert.That(results, Is.EqualTo(new[] { (0, 3), (4, 9), (10, 16) }).AsCollection); } } From 4f9dfa7d70da9af245685bf39c9bc6f787ccac36 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 20 Feb 2026 22:08:53 +0100 Subject: [PATCH 22/41] Dotnet format --- Source/BookGen.Vfs/MultiReadScopeFileSystem.cs | 4 ++-- Source/BookGen/Commands/SchemasCommand.cs | 2 +- Test/Bookgen.Tests/Cli/UT_CommandRunner.cs | 6 +++--- Test/Bookgen.Tests/Commands/CommandTestBase.cs | 2 +- Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs | 4 ++-- Test/Bookgen.Tests/Lib/UT_ImgService.cs | 4 ++-- Test/Bookgen.Tests/Lib/UT_LevelList.cs | 2 +- Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs | 6 +++--- Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs | 4 ++-- Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs | 6 +++--- Test/Bookgen.Tests/TestDocumentation.cs | 8 ++++---- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs b/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs index 29af0b89..d472c648 100644 --- a/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs +++ b/Source/BookGen.Vfs/MultiReadScopeFileSystem.cs @@ -14,7 +14,7 @@ private string Resolve(string path) foreach (var scope in _scopes) { var fullPath = Path.GetFullPath(Path.Combine(scope, path)); - if (File.Exists(fullPath)) + if (File.Exists(fullPath)) return fullPath; } throw new InvalidOperationException($"{path} can't be found"); @@ -25,7 +25,7 @@ public MultiReadScopeFileSystem(params IEnumerable scopes) _scopes = new HashSet(scopes); } - public string Scope + public string Scope { get => string.Join(Environment.NewLine, _scopes); set => throw new NotSupportedException(); diff --git a/Source/BookGen/Commands/SchemasCommand.cs b/Source/BookGen/Commands/SchemasCommand.cs index ae8491c0..8ec9557d 100644 --- a/Source/BookGen/Commands/SchemasCommand.cs +++ b/Source/BookGen/Commands/SchemasCommand.cs @@ -23,7 +23,7 @@ internal sealed class SchemasCommand : Command { private readonly IWritableFileSystem _writableFileSystem; private readonly ILogger _logger; - + public SchemasCommand(IWritableFileSystem writableFileSystem, ILogger logger) { _writableFileSystem = writableFileSystem; diff --git a/Test/Bookgen.Tests/Cli/UT_CommandRunner.cs b/Test/Bookgen.Tests/Cli/UT_CommandRunner.cs index ebf1293c..e6318eb2 100644 --- a/Test/Bookgen.Tests/Cli/UT_CommandRunner.cs +++ b/Test/Bookgen.Tests/Cli/UT_CommandRunner.cs @@ -15,9 +15,9 @@ namespace Bookgen.Tests.Cli; [TestFixture] internal class UT_CommandRunner { - private CommandRunner _sut; - private Mock _serviceProviderMock; - private Mock _loggerMock; + private CommandRunner? _sut; + private Mock? _serviceProviderMock; + private Mock? _loggerMock; private sealed class Dependency { diff --git a/Test/Bookgen.Tests/Commands/CommandTestBase.cs b/Test/Bookgen.Tests/Commands/CommandTestBase.cs index 6ea47afe..921d66f1 100644 --- a/Test/Bookgen.Tests/Commands/CommandTestBase.cs +++ b/Test/Bookgen.Tests/Commands/CommandTestBase.cs @@ -21,7 +21,7 @@ internal abstract class CommandTestBase where TCommand : ICommand protected readonly Mock CommandRunnerProxyMock = new Mock(MockBehavior.Strict); protected readonly Mock FilesystemFactoryMock = new Mock(MockBehavior.Strict); - protected ICommand Command { get; private set; } + protected ICommand? Command { get; private set; } [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs b/Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs index e4ee8042..83fa6a33 100644 --- a/Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs +++ b/Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs @@ -13,8 +13,8 @@ namespace Bookgen.Tests.Lib; internal class UT_FootNoteReindexer { - private FootNoteReindexer _sut; - private Mock _logMock; + private FootNoteReindexer? _sut; + private Mock? _logMock; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_ImgService.cs b/Test/Bookgen.Tests/Lib/UT_ImgService.cs index bd170f36..847eecd6 100644 --- a/Test/Bookgen.Tests/Lib/UT_ImgService.cs +++ b/Test/Bookgen.Tests/Lib/UT_ImgService.cs @@ -15,8 +15,8 @@ namespace Bookgen.Tests.Lib; [TestFixture] internal class UT_ImgService { - private EmbeddedTestFileSystem _testFolder; - private Mock _mockLogger; + private EmbeddedTestFileSystem? _testFolder; + private Mock? _mockLogger; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_LevelList.cs b/Test/Bookgen.Tests/Lib/UT_LevelList.cs index 80efd6c3..fd105b88 100644 --- a/Test/Bookgen.Tests/Lib/UT_LevelList.cs +++ b/Test/Bookgen.Tests/Lib/UT_LevelList.cs @@ -35,7 +35,7 @@ public string ToFormatedString(string prefix = "", StringBuilder? sb = null) } } - private TestLevelList _root; + private TestLevelList? _root; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs index ec7f7a3e..e763b174 100644 --- a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs +++ b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs @@ -13,9 +13,9 @@ namespace Bookgen.Tests.Lib; internal class UT_MarkdownConverter { - private Mock _imgServiceMock; - private string _markdown; - private string _soruceCode; + private Mock? _imgServiceMock; + private string? _markdown; + private string? _soruceCode; private readonly IEqualityComparer comparer = new LineEndingIgnoreComparer(); [SetUp] diff --git a/Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs b/Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs index 68b2a954..283ad15f 100644 --- a/Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs +++ b/Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs @@ -15,8 +15,8 @@ namespace Bookgen.Tests.Lib; [TestFixture] internal class UT_SerializedObjectValidator { - private Mock _fileSystem; - private SerializedObjectValidator _validator; + private Mock? _fileSystem; + private SerializedObjectValidator? _validator; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs b/Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs index 73cb0b5d..7d84fae4 100644 --- a/Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs +++ b/Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs @@ -16,9 +16,9 @@ namespace Bookgen.Tests.Lib; [TestFixture] internal class UT_TemplateEngine { - private TemplateEngine _sut; - private TestLogger _logger; - private Mock _assetSourceMock; + private TemplateEngine? _sut; + private TestLogger? _logger; + private Mock? _assetSourceMock; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/TestDocumentation.cs b/Test/Bookgen.Tests/TestDocumentation.cs index 20859ab7..579b59db 100644 --- a/Test/Bookgen.Tests/TestDocumentation.cs +++ b/Test/Bookgen.Tests/TestDocumentation.cs @@ -14,10 +14,10 @@ namespace Bookgen.Tests; [TestFixture] internal class TestDocumentation { - private Mock _serviceProviderMock; - private Mock _loggerMock; - private HelpProvider _helpProvider; - private CommandRunnerProxy _commandRunnerProxy; + private Mock? _serviceProviderMock; + private Mock? _loggerMock; + private HelpProvider? _helpProvider; + private CommandRunnerProxy? _commandRunnerProxy; [SetUp] public void Setup() From 5dcf7d18dc6e5b0522e586522961caa5b9f5c8a6 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Fri, 20 Feb 2026 22:12:03 +0100 Subject: [PATCH 23/41] Dependency update --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index aec8df1b..8389417a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ true - + @@ -16,7 +16,7 @@ - + From 4657d3d7d6d9d9bc09bdf9670199e1abb307acda Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sat, 21 Feb 2026 11:53:45 +0100 Subject: [PATCH 24/41] Added new tests --- .../Bookgen.Tests/Lib/UT_MarkdownConverter.cs | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs index e763b174..360e6987 100644 --- a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs +++ b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs @@ -13,9 +13,9 @@ namespace Bookgen.Tests.Lib; internal class UT_MarkdownConverter { - private Mock? _imgServiceMock; - private string? _markdown; - private string? _soruceCode; + private Mock _imgServiceMock; + private string _markdown; + private string _soruceCode; private readonly IEqualityComparer comparer = new LineEndingIgnoreComparer(); [SetUp] @@ -539,4 +539,64 @@ public void EnsureThat_Toc_Limited_RendersCorrectly() Assert.That(result, Is.EqualTo(expected).Using(comparer)); } + + [Test] + public void EnsureThat_TerminalRenderingWorks() + { + using var settings = new RenderSettings(_imgServiceMock.Object) + { + CssClasses = new CssClasses(), + DeleteFirstH1 = false, + HostUrl = null, + PrismJsInterop = null, + AutoEmbedSupportedLinks = true, + }; + + using var sut = new MarkdownConverter(settings); + + string input = """ + ```terminal + this is terminal output + ``` + """; + + string expected = """ +
    +
    $
    +
    this is terminal output
    +
    + """; + + string result = sut.RenderMarkdownToHtml(input); + + Assert.That(result, Is.EqualTo(expected).Using(comparer)); + } + + [Test] + public void EnsrureThat_Youtube_Autolink_Works() + { + string input = """ + https://www.youtube.com/watch?v=D5ivt3hNAW8 + """; + + string expected = """ +

    + + """; + + using var settings = new RenderSettings(_imgServiceMock.Object) + { + CssClasses = new CssClasses(), + DeleteFirstH1 = false, + HostUrl = null, + PrismJsInterop = null, + AutoEmbedSupportedLinks = true, + }; + + using var sut = new MarkdownConverter(settings); + + string result = sut.RenderMarkdownToHtml(input); + + Assert.That(result, Is.EqualTo(expected).Using(comparer)); + } } From bf3d03cacea340667a0f17a0b419d39ae4a22981 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Mon, 23 Feb 2026 19:17:28 +0100 Subject: [PATCH 25/41] Fix nullable issues --- Test/Bookgen.Tests/Cli/UT_CommandRunner.cs | 6 +++--- Test/Bookgen.Tests/Commands/CommandTestBase.cs | 2 +- Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs | 4 ++-- Test/Bookgen.Tests/Lib/UT_ImgService.cs | 4 ++-- Test/Bookgen.Tests/Lib/UT_LevelList.cs | 2 +- Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs | 4 ++-- Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs | 6 +++--- Test/Bookgen.Tests/TestDocumentation.cs | 8 ++++---- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Test/Bookgen.Tests/Cli/UT_CommandRunner.cs b/Test/Bookgen.Tests/Cli/UT_CommandRunner.cs index e6318eb2..ebf1293c 100644 --- a/Test/Bookgen.Tests/Cli/UT_CommandRunner.cs +++ b/Test/Bookgen.Tests/Cli/UT_CommandRunner.cs @@ -15,9 +15,9 @@ namespace Bookgen.Tests.Cli; [TestFixture] internal class UT_CommandRunner { - private CommandRunner? _sut; - private Mock? _serviceProviderMock; - private Mock? _loggerMock; + private CommandRunner _sut; + private Mock _serviceProviderMock; + private Mock _loggerMock; private sealed class Dependency { diff --git a/Test/Bookgen.Tests/Commands/CommandTestBase.cs b/Test/Bookgen.Tests/Commands/CommandTestBase.cs index 921d66f1..6ea47afe 100644 --- a/Test/Bookgen.Tests/Commands/CommandTestBase.cs +++ b/Test/Bookgen.Tests/Commands/CommandTestBase.cs @@ -21,7 +21,7 @@ internal abstract class CommandTestBase where TCommand : ICommand protected readonly Mock CommandRunnerProxyMock = new Mock(MockBehavior.Strict); protected readonly Mock FilesystemFactoryMock = new Mock(MockBehavior.Strict); - protected ICommand? Command { get; private set; } + protected ICommand Command { get; private set; } [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs b/Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs index 83fa6a33..e4ee8042 100644 --- a/Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs +++ b/Test/Bookgen.Tests/Lib/UT_FootNoteReindexer.cs @@ -13,8 +13,8 @@ namespace Bookgen.Tests.Lib; internal class UT_FootNoteReindexer { - private FootNoteReindexer? _sut; - private Mock? _logMock; + private FootNoteReindexer _sut; + private Mock _logMock; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_ImgService.cs b/Test/Bookgen.Tests/Lib/UT_ImgService.cs index 847eecd6..bd170f36 100644 --- a/Test/Bookgen.Tests/Lib/UT_ImgService.cs +++ b/Test/Bookgen.Tests/Lib/UT_ImgService.cs @@ -15,8 +15,8 @@ namespace Bookgen.Tests.Lib; [TestFixture] internal class UT_ImgService { - private EmbeddedTestFileSystem? _testFolder; - private Mock? _mockLogger; + private EmbeddedTestFileSystem _testFolder; + private Mock _mockLogger; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_LevelList.cs b/Test/Bookgen.Tests/Lib/UT_LevelList.cs index fd105b88..80efd6c3 100644 --- a/Test/Bookgen.Tests/Lib/UT_LevelList.cs +++ b/Test/Bookgen.Tests/Lib/UT_LevelList.cs @@ -35,7 +35,7 @@ public string ToFormatedString(string prefix = "", StringBuilder? sb = null) } } - private TestLevelList? _root; + private TestLevelList _root; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs b/Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs index 283ad15f..68b2a954 100644 --- a/Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs +++ b/Test/Bookgen.Tests/Lib/UT_SerializedObjectValidator.cs @@ -15,8 +15,8 @@ namespace Bookgen.Tests.Lib; [TestFixture] internal class UT_SerializedObjectValidator { - private Mock? _fileSystem; - private SerializedObjectValidator? _validator; + private Mock _fileSystem; + private SerializedObjectValidator _validator; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs b/Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs index 7d84fae4..73cb0b5d 100644 --- a/Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs +++ b/Test/Bookgen.Tests/Lib/UT_TemplateEngine.cs @@ -16,9 +16,9 @@ namespace Bookgen.Tests.Lib; [TestFixture] internal class UT_TemplateEngine { - private TemplateEngine? _sut; - private TestLogger? _logger; - private Mock? _assetSourceMock; + private TemplateEngine _sut; + private TestLogger _logger; + private Mock _assetSourceMock; [SetUp] public void Setup() diff --git a/Test/Bookgen.Tests/TestDocumentation.cs b/Test/Bookgen.Tests/TestDocumentation.cs index 579b59db..20859ab7 100644 --- a/Test/Bookgen.Tests/TestDocumentation.cs +++ b/Test/Bookgen.Tests/TestDocumentation.cs @@ -14,10 +14,10 @@ namespace Bookgen.Tests; [TestFixture] internal class TestDocumentation { - private Mock? _serviceProviderMock; - private Mock? _loggerMock; - private HelpProvider? _helpProvider; - private CommandRunnerProxy? _commandRunnerProxy; + private Mock _serviceProviderMock; + private Mock _loggerMock; + private HelpProvider _helpProvider; + private CommandRunnerProxy _commandRunnerProxy; [SetUp] public void Setup() From 892c1a16f640939c25e51dab955e7e8e04e0a009 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Mon, 23 Feb 2026 19:43:49 +0100 Subject: [PATCH 26/41] Spectre logger used globaly in both executables --- Directory.Packages.props | 1 + .../BookGen.Shell.Shared.csproj | 5 + .../Loging/ConsoleLogProvider.cs | 4 +- .../BookGen.Shell.Shared/packages.lock.json | 12 +++ .../BookGen.Shellprog.csproj | 4 +- Source/BookGen.Shellprog/Program.cs | 8 +- Source/BookGen.Shellprog/packages.lock.json | 102 ++++-------------- Source/BookGen/BookGen.csproj | 3 +- .../Infrastructure/Loging/DumyLogScope.cs | 14 --- Source/BookGen/Program.cs | 1 + Source/BookGen/packages.lock.json | 96 +++-------------- Source/Bookgen.Lib/packages.lock.json | 7 ++ 12 files changed, 75 insertions(+), 182 deletions(-) rename Source/{BookGen/Infrastructure => BookGen.Shell.Shared}/Loging/ConsoleLogProvider.cs (97%) delete mode 100644 Source/BookGen/Infrastructure/Loging/DumyLogScope.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 8389417a..0e0ed352 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,6 +9,7 @@ + diff --git a/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj b/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj index 96febe16..399331cc 100644 --- a/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj +++ b/Source/BookGen.Shell.Shared/BookGen.Shell.Shared.csproj @@ -11,6 +11,11 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/BookGen/Infrastructure/Loging/ConsoleLogProvider.cs b/Source/BookGen.Shell.Shared/Loging/ConsoleLogProvider.cs similarity index 97% rename from Source/BookGen/Infrastructure/Loging/ConsoleLogProvider.cs rename to Source/BookGen.Shell.Shared/Loging/ConsoleLogProvider.cs index d49f4fe4..02534223 100644 --- a/Source/BookGen/Infrastructure/Loging/ConsoleLogProvider.cs +++ b/Source/BookGen.Shell.Shared/Loging/ConsoleLogProvider.cs @@ -9,7 +9,7 @@ using Spectre.Console; -namespace BookGen.Infrastructure.Loging; +namespace BookGen.Shell.Shared.Loging; public sealed class ConsoleLogProvider : ILoggerProvider { @@ -54,7 +54,7 @@ public ConsoleLogger(string categoryName) } public IDisposable? BeginScope(TState state) where TState : notnull - => new DumyLogScope(); + => null; public void Dispose() { diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json index e18b60dc..0bbd134f 100644 --- a/Source/BookGen.Shell.Shared/packages.lock.json +++ b/Source/BookGen.Shell.Shared/packages.lock.json @@ -11,6 +11,18 @@ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, + "Spectre.Console": { + "type": "Direct", + "requested": "[0.54.0, )", + "resolved": "0.54.0", + "contentHash": "StDXCFayfy0yB1xzUHT2tgEpV1/HFTiS4JgsAQS49EYTfMixSwwucaQs/bIOCwXjWwIQTMuxjUIxcB5XsJkFJA==" + }, + "Spectre.Console.Analyzer": { + "type": "Direct", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "TojbwWASRf3PMUsotXa8MQ9SNwEwWZZ5bAYf1eLv+jiDsxaD/n7TKTi3da6z4+m8uZHri2i+BVRuLj5w+6j64Q==" + }, "Webmaster442.WindowsTerminal": { "type": "Direct", "requested": "[4.1.1, )", diff --git a/Source/BookGen.Shellprog/BookGen.Shellprog.csproj b/Source/BookGen.Shellprog/BookGen.Shellprog.csproj index fdd92f4b..bf548b8a 100644 --- a/Source/BookGen.Shellprog/BookGen.Shellprog.csproj +++ b/Source/BookGen.Shellprog/BookGen.Shellprog.csproj @@ -13,8 +13,10 @@ + + - + all diff --git a/Source/BookGen.Shellprog/Program.cs b/Source/BookGen.Shellprog/Program.cs index b4ff9451..c42798a4 100644 --- a/Source/BookGen.Shellprog/Program.cs +++ b/Source/BookGen.Shellprog/Program.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using BookGen.Cli; +using BookGen.Shell.Shared.Loging; using BookGen.Shellprog; using Microsoft.Extensions.DependencyInjection; @@ -11,7 +12,12 @@ using Spectre.Console; -using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); +using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => +{ + builder.ClearProviders(); + builder.AddFilter(level => level >= LogLevel.Information); + builder.AddProvider(new ConsoleLogProvider()); +}); ILogger logger = loggerFactory.CreateLogger("BookGen.Shell"); diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json index 0a12cd5d..3fb26f16 100644 --- a/Source/BookGen.Shellprog/packages.lock.json +++ b/Source/BookGen.Shellprog/packages.lock.json @@ -2,6 +2,21 @@ "version": 2, "dependencies": { "net10.0": { + "Microsoft.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Direct", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" + }, "Microsoft.Extensions.Logging": { "type": "Direct", "requested": "[10.0.3, )", @@ -13,17 +28,13 @@ "Microsoft.Extensions.Options": "10.0.3" } }, - "Microsoft.Extensions.Logging.Console": { + "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", "requested": "[10.0.3, )", "resolved": "10.0.3", - "contentHash": "7sRvbBH3icaV9qil8fyBKmR+yEZ0yDU6Bq/KgBwswS36164yGaxbf7Kd4hD1iHZ2GfvyoJWWqBUBm9QX/IASAQ==", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Configuration": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Spectre.Console": { @@ -38,55 +49,6 @@ "resolved": "1.0.0", "contentHash": "TojbwWASRf3PMUsotXa8MQ9SNwEwWZZ5bAYf1eLv+jiDsxaD/n7TKTi3da6z4+m8uZHri2i+BVRuLj5w+6j64Q==" }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.3" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" - } - }, - "Microsoft.Extensions.Logging.Configuration": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "PBlaoYeusaxNYyN4WFjzcXWlUDSvLUPxy/e6oP1SONOOYA/oBWT2uBmFGJMV9VTtXiXXxCB39LqlYWbsWE4UKA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Configuration.Binder": "10.0.3", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.3" - } - }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "10.0.3", @@ -96,18 +58,6 @@ "Microsoft.Extensions.Primitives": "10.0.3" } }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bn6QoBbbvwmzLIFyxrnL2/e+sqoNUOGbHyfWK9DPONMv1mDCYHm/C7MusYASM31b2lUx6OiDmonb3v+dv5t0nA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Configuration.Binder": "10.0.3", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" - } - }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "10.0.3", @@ -128,27 +78,13 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )", + "Spectre.Console": "[0.54.0, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, "bookgen.vfs": { "type": "Project" }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "CentralTransitive", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "CentralTransitive", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" - } - }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", "requested": "[4.1.1, )", diff --git a/Source/BookGen/BookGen.csproj b/Source/BookGen/BookGen.csproj index f9c52325..d7f7212d 100644 --- a/Source/BookGen/BookGen.csproj +++ b/Source/BookGen/BookGen.csproj @@ -23,9 +23,10 @@ + + - diff --git a/Source/BookGen/Infrastructure/Loging/DumyLogScope.cs b/Source/BookGen/Infrastructure/Loging/DumyLogScope.cs deleted file mode 100644 index 4124d928..00000000 --- a/Source/BookGen/Infrastructure/Loging/DumyLogScope.cs +++ /dev/null @@ -1,14 +0,0 @@ -//----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor -// This code is licensed under MIT license (see LICENSE for details) -//----------------------------------------------------------------------------- - -namespace BookGen.Infrastructure.Loging; - -internal sealed class DumyLogScope : IDisposable -{ - public void Dispose() - { - //empty - } -} diff --git a/Source/BookGen/Program.cs b/Source/BookGen/Program.cs index a48ae3ff..76b025ec 100644 --- a/Source/BookGen/Program.cs +++ b/Source/BookGen/Program.cs @@ -10,6 +10,7 @@ using BookGen.Commands; using BookGen.Infrastructure; using BookGen.Infrastructure.Loging; +using BookGen.Shell.Shared.Loging; using BookGen.Vfs; using Microsoft.Extensions.DependencyInjection; diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index 92ab81e4..3c5155a9 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -8,6 +8,21 @@ "resolved": "0.45.0", "contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg==" }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Direct", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" + }, "Microsoft.Extensions.Logging": { "type": "Direct", "requested": "[10.0.3, )", @@ -28,19 +43,6 @@ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, - "Microsoft.Extensions.Logging.Console": { - "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "7sRvbBH3icaV9qil8fyBKmR+yEZ0yDU6Bq/KgBwswS36164yGaxbf7Kd4hD1iHZ2GfvyoJWWqBUBm9QX/IASAQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Configuration": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3" - } - }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Direct", "requested": "[3.0.1, )", @@ -111,55 +113,6 @@ "Microsoft.ClearScript.Core": "7.5.0" } }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.3" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" - } - }, - "Microsoft.Extensions.Logging.Configuration": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "PBlaoYeusaxNYyN4WFjzcXWlUDSvLUPxy/e6oP1SONOOYA/oBWT2uBmFGJMV9VTtXiXXxCB39LqlYWbsWE4UKA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Configuration.Binder": "10.0.3", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.3" - } - }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "10.0.3", @@ -169,18 +122,6 @@ "Microsoft.Extensions.Primitives": "10.0.3" } }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bn6QoBbbvwmzLIFyxrnL2/e+sqoNUOGbHyfWK9DPONMv1mDCYHm/C7MusYASM31b2lUx6OiDmonb3v+dv5t0nA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Configuration.Binder": "10.0.3", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" - } - }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "10.0.3", @@ -250,6 +191,7 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )", + "Spectre.Console": "[0.54.0, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -281,12 +223,6 @@ "resolved": "7.5.0", "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "CentralTransitive", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" - }, "SkiaSharp": { "type": "CentralTransitive", "requested": "[3.119.2, )", diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index c06f640c..9f69682a 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -159,12 +159,19 @@ "bookgen.shell.shared": { "type": "Project", "dependencies": { + "Spectre.Console": "[0.54.0, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, "bookgen.vfs": { "type": "Project" }, + "Spectre.Console": { + "type": "CentralTransitive", + "requested": "[0.54.0, )", + "resolved": "0.54.0", + "contentHash": "StDXCFayfy0yB1xzUHT2tgEpV1/HFTiS4JgsAQS49EYTfMixSwwucaQs/bIOCwXjWwIQTMuxjUIxcB5XsJkFJA==" + }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", "requested": "[4.1.1, )", From 9386a461eb4b1e6824990a668e94c7a6091306bd Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Tue, 24 Feb 2026 17:25:46 +0100 Subject: [PATCH 27/41] Improvement of ZIP asset handling API --- Source/BookGen.Contents/BookGen.Contents.csproj | 5 ++++- Source/BookGen.Vfs/IAssetSource.cs | 2 +- Source/BookGen.Vfs/ZipAssetSoruce.cs | 16 ++-------------- Source/Bookgen.Lib/BookEnvironment.cs | 4 ++-- .../Bookgen.Lib/Pipeline/Epub/CreateFontFiles.cs | 6 +++--- Test/Bookgen.Tests/TestEnvironment.cs | 4 ++-- 6 files changed, 14 insertions(+), 23 deletions(-) diff --git a/Source/BookGen.Contents/BookGen.Contents.csproj b/Source/BookGen.Contents/BookGen.Contents.csproj index 87d56789..287e4ea8 100644 --- a/Source/BookGen.Contents/BookGen.Contents.csproj +++ b/Source/BookGen.Contents/BookGen.Contents.csproj @@ -15,6 +15,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -32,7 +35,7 @@ $([System.IO.Path]::GetFullPath('$(OutputPath)\assets.zip')) - + diff --git a/Source/BookGen.Vfs/IAssetSource.cs b/Source/BookGen.Vfs/IAssetSource.cs index aef66f88..7b6c0601 100644 --- a/Source/BookGen.Vfs/IAssetSource.cs +++ b/Source/BookGen.Vfs/IAssetSource.cs @@ -13,7 +13,7 @@ public interface IAssetSource IReadOnlyList AssetNames { get; } - byte[] GetBinaryAsset(string name); + Stream GetBinaryAssetStream(string name); string GetAsset(string name) { diff --git a/Source/BookGen.Vfs/ZipAssetSoruce.cs b/Source/BookGen.Vfs/ZipAssetSoruce.cs index 18897aa1..094e7bfb 100644 --- a/Source/BookGen.Vfs/ZipAssetSoruce.cs +++ b/Source/BookGen.Vfs/ZipAssetSoruce.cs @@ -70,25 +70,13 @@ public bool TryGetAsset(string name, [NotNullWhen(true)] out string? content) return true; } - public byte[] GetBinaryAsset(string name) + public Stream GetBinaryAssetStream(string name) { ObjectDisposedException.ThrowIf(_disposed, nameof(_zip)); lock (_lock) { ZipArchiveEntry? entry = _zip.GetEntry(name) ?? throw new InvalidOperationException($"{name} was not found in assets"); - byte[] data = new byte[entry.Length]; - using Stream dataStream = entry.Open(); - byte[] buffer = ArrayPool.Shared.Rent(4096); - int read = 0; - int offset = 0; - while ((read = dataStream.Read(buffer, 0, buffer.Length)) > 0) - { - Array.Copy(buffer, 0, data, offset, read); - offset += read; - } - ArrayPool.Shared.Return(buffer, true); - - return data; + return entry.Open(); } } } diff --git a/Source/Bookgen.Lib/BookEnvironment.cs b/Source/Bookgen.Lib/BookEnvironment.cs index 93f188b1..b1234776 100644 --- a/Source/Bookgen.Lib/BookEnvironment.cs +++ b/Source/Bookgen.Lib/BookEnvironment.cs @@ -146,13 +146,13 @@ public bool TryGetAsset(string name, [NotNullWhen(true)] out string? content) return false; } - public byte[] GetBinaryAsset(string name) + public Stream GetBinaryAssetStream(string name) { foreach (IAssetSource assetsource in _assets) { try { - return assetsource.GetBinaryAsset(name); + return assetsource.GetBinaryAssetStream(name); } catch (InvalidOperationException) { diff --git a/Source/Bookgen.Lib/Pipeline/Epub/CreateFontFiles.cs b/Source/Bookgen.Lib/Pipeline/Epub/CreateFontFiles.cs index a052862f..e48cfd4a 100644 --- a/Source/Bookgen.Lib/Pipeline/Epub/CreateFontFiles.cs +++ b/Source/Bookgen.Lib/Pipeline/Epub/CreateFontFiles.cs @@ -20,9 +20,9 @@ public CreateFontFiles(EpubState state) : base(state) public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { logger.LogInformation("Creating EPUB font files..."); - await State.EpubFile.AddAsync("JetBrainsMono-Regular.ttf", environment.GetBinaryAsset("JetBrainsMono-Regular.ttf"), CompressionLevel.Optimal); - await State.EpubFile.AddAsync("OpenSans-Regular.ttf", environment.GetBinaryAsset("OpenSans-Regular.ttf"), CompressionLevel.Optimal); - await State.EpubFile.AddAsync("Nunito-Bold.ttf", environment.GetBinaryAsset("Nunito-Bold.ttf"), CompressionLevel.Optimal); + await State.EpubFile.AddAsync("JetBrainsMono-Regular.ttf", environment.GetBinaryAssetStream("JetBrainsMono-Regular.ttf"), CompressionLevel.Optimal); + await State.EpubFile.AddAsync("OpenSans-Regular.ttf", environment.GetBinaryAssetStream("OpenSans-Regular.ttf"), CompressionLevel.Optimal); + await State.EpubFile.AddAsync("Nunito-Bold.ttf", environment.GetBinaryAssetStream("Nunito-Bold.ttf"), CompressionLevel.Optimal); State.PackageItems.AddRange([ new PackageItem diff --git a/Test/Bookgen.Tests/TestEnvironment.cs b/Test/Bookgen.Tests/TestEnvironment.cs index d5ab71f8..60efe72b 100644 --- a/Test/Bookgen.Tests/TestEnvironment.cs +++ b/Test/Bookgen.Tests/TestEnvironment.cs @@ -45,6 +45,6 @@ public bool TryGetAsset(string name, [NotNullWhen(true)] out string? content) public static bool IsBookGenFolder(string folder) => false; - public byte[] GetBinaryAsset(string name) - => _assetSoruce.GetBinaryAsset(name); + public Stream GetBinaryAssetStream(string name) + => _assetSoruce.GetBinaryAssetStream(name); } From ab2edc4db2746710f330f8544810d68a3cfc65e0 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Tue, 24 Feb 2026 17:26:04 +0100 Subject: [PATCH 28/41] Spell checking: dictionary compile --- Assets/.gitignore | 3 +++ Assets/compile-dictionaries.ps1 | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 Assets/.gitignore create mode 100644 Assets/compile-dictionaries.ps1 diff --git a/Assets/.gitignore b/Assets/.gitignore new file mode 100644 index 00000000..fba3ac75 --- /dev/null +++ b/Assets/.gitignore @@ -0,0 +1,3 @@ +dictionaries/*.dic +dictionaries/*.aff +dictionaries.zip \ No newline at end of file diff --git a/Assets/compile-dictionaries.ps1 b/Assets/compile-dictionaries.ps1 new file mode 100644 index 00000000..ec0bc423 --- /dev/null +++ b/Assets/compile-dictionaries.ps1 @@ -0,0 +1,25 @@ +$dictionariesPath = ".\dictionaries" +if (-not (Test-Path $dictionariesPath)) { + New-Item -ItemType Directory -Path $dictionariesPath | Out-Null +} + +cd $dictionariesPath +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.aff" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.dic" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_US.aff" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_US.dic" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hu_HU/hu_HU.aff" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hu_HU/hu_HU.dic" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hr_HR/hr_HR.aff" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hr_HR/hr_HR.dic" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/it_IT/it_IT.aff" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/it_IT/it_IT.dic" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/nl_NL/nl_NL.aff" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/nl_NL/nl_NL.dic" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/de/de_DE_frami.aff" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/de/de_DE_frami.dic" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/es/es_ES.aff" +curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/es/es_ES.dic" +cd .. + +Compress-Archive -Path "$dictionariesPath\*" -DestinationPath "dictionaries.zip" -CompressionLevel Optimal From d34e1ecc3af8866bc102a321d934353eafe25d20 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Tue, 24 Feb 2026 17:30:40 +0100 Subject: [PATCH 29/41] md2html: fix no syntax option --- Source/BookGen/Commands/Md2HtmlCommand.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/BookGen/Commands/Md2HtmlCommand.cs b/Source/BookGen/Commands/Md2HtmlCommand.cs index 609c9b7e..daa1a210 100644 --- a/Source/BookGen/Commands/Md2HtmlCommand.cs +++ b/Source/BookGen/Commands/Md2HtmlCommand.cs @@ -136,12 +136,12 @@ public override int Execute(Md2HtmlArguments arguments, IReadOnlyList co CssClasses = new CssClasses(), OffsetHeadingsBy = 0, AutoEmbedSupportedLinks = !arguments.NoEmbed, - PrismJsInterop = new PrismJsInterop(_assetSource) + PrismJsInterop = arguments.NoSyntax ? null : new PrismJsInterop(_assetSource) }; - using var markdonwConverter = new MarkdownConverter(settings); + using var markdownConverter = new MarkdownConverter(settings); - string? mdcontent = markdonwConverter.RenderMarkdownToHtml(md); + string? mdcontent = markdownConverter.RenderMarkdownToHtml(md); string rendered; if (arguments.RawHtml) From 94a9ddfe7ddc83ca267c802bbe98412c81595df3 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Wed, 25 Feb 2026 19:28:35 +0100 Subject: [PATCH 30/41] Spell check command --- Commands.md | 27 +++ Directory.Packages.props | 1 + Source/BookGen.Cli/CommandRunner.cs | 12 +- Source/BookGen.Vfs/EmptyAssetSource.cs | 25 +++ Source/BookGen/BookGen.csproj | 1 + Source/BookGen/Commands/SpellCheckCommand.cs | 200 +++++++++++++++++++ Source/BookGen/Program.cs | 9 + Source/BookGen/packages.lock.json | 6 + 8 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 Source/BookGen.Vfs/EmptyAssetSource.cs create mode 100644 Source/BookGen/Commands/SpellCheckCommand.cs diff --git a/Commands.md b/Commands.md index 30923a51..da1ba6b2 100644 --- a/Commands.md +++ b/Commands.md @@ -621,6 +621,33 @@ BookGen Shortcut This command is only supported on Windows OS. +# Spellcheck + +Perform spell check on a given markdown file or text file. The command will print the misspelled words to the console. + +``` +BookGen Spellcheck -i [-l ] [-v] +BookGen Spellcheck --input [--language ] [--verbose] +``` + +Arguments: + +* `-i`, `--input`: + + Required argument. Specifies the input file path. The file must be a markdown file or a text file. + +* `-l`, `--language`: + + Optional argument. Specifies the language to use for spell checking. The value must be a valid language code, like en_US or hu_HU. If not specified, then en_US will be used as the default language. + +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-ld`, `--list-dictionaires` + + Optional argument. When specified, the command will list all available dictionaries and exit. + # Stats Displays various statistics about the bookgen project. diff --git a/Directory.Packages.props b/Directory.Packages.props index 0e0ed352..ebcdc946 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -28,6 +28,7 @@ + diff --git a/Source/BookGen.Cli/CommandRunner.cs b/Source/BookGen.Cli/CommandRunner.cs index 8484600a..dca581b3 100644 --- a/Source/BookGen.Cli/CommandRunner.cs +++ b/Source/BookGen.Cli/CommandRunner.cs @@ -106,13 +106,19 @@ private ICommand CreateCommand(string commandName) .OrderByDescending(c => c.GetParameters().Length) .First(); - List contructorParameters = new(); + List constructorParameters = new(); foreach (ParameterInfo param in constructor.GetParameters()) { - contructorParameters.Add(_serviceProvider.GetRequiredService(param.ParameterType)); + FromKeyedServicesAttribute? keyAttribute = param.GetCustomAttribute(); + + object parameterInstance = keyAttribute != null + ? _serviceProvider.GetRequiredKeyedService(param.ParameterType, keyAttribute.Key) + : _serviceProvider.GetRequiredService(param.ParameterType); + + constructorParameters.Add(parameterInstance); } - var instance = Activator.CreateInstance(_commands[commandName], contructorParameters.ToArray()) + var instance = Activator.CreateInstance(_commands[commandName], constructorParameters.ToArray()) ?? throw new InvalidOperationException(); return (ICommand)instance; diff --git a/Source/BookGen.Vfs/EmptyAssetSource.cs b/Source/BookGen.Vfs/EmptyAssetSource.cs new file mode 100644 index 00000000..4badaba0 --- /dev/null +++ b/Source/BookGen.Vfs/EmptyAssetSource.cs @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2025 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +namespace BookGen.Vfs; + +public sealed class EmptyAssetSource : IAssetSource +{ + public IReadOnlyList AssetNames + => Array.Empty(); + + public Stream GetBinaryAssetStream(string name) + { + return Stream.Null; + } + + public bool TryGetAsset(string name, [NotNullWhen(true)] out string? content) + { + content = null; + return false; + } +} diff --git a/Source/BookGen/BookGen.csproj b/Source/BookGen/BookGen.csproj index d7f7212d..a9a7fab4 100644 --- a/Source/BookGen/BookGen.csproj +++ b/Source/BookGen/BookGen.csproj @@ -33,6 +33,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/BookGen/Commands/SpellCheckCommand.cs b/Source/BookGen/Commands/SpellCheckCommand.cs new file mode 100644 index 00000000..e7c3cb78 --- /dev/null +++ b/Source/BookGen/Commands/SpellCheckCommand.cs @@ -0,0 +1,200 @@ +using System.Diagnostics.Contracts; +using System.Text; + +using BookGen.Cli; +using BookGen.Cli.Annotations; +using BookGen.Vfs; + +using Markdig.Syntax; +using Markdig.Syntax.Inlines; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +using WeCantSpell.Hunspell; + +namespace BookGen.Commands; + +[CommandName("spellcheck")] +internal sealed class SpellCheckCommand : AsyncCommand +{ + public sealed class SpellCheckArguments : ArgumentsBase, IVerbosablityToggle + { + [Switch("i", "input")] + public string InputFile { get; set; } = string.Empty; + + [Switch("v", "verbose")] + public bool Verbose { get; set; } + + [Switch("l", "language")] + public string Language { get; set; } = "en_US"; + + [Switch("-ld", "--list-dictionaires")] + public bool DictionariesDisplay { get; set; } = false; + + public override ValidationResult Validate(IValidationContext context) + { + if (!context.FileSystem.FileExists(InputFile)) + return ValidationResult.Error($"Input file '{InputFile}' does not exist."); + + var extension = Path.GetExtension(InputFile); + + if (!string.Equals(extension, ".md", StringComparison.OrdinalIgnoreCase) + && !string.Equals(extension, ".txt", StringComparison.OrdinalIgnoreCase)) + { + return ValidationResult.Error($"Input file '{InputFile}' is not a markdown file."); + } + + return ValidationResult.Ok(); + } + } + + private readonly IAssetSource _dictionaries; + private readonly ILogger _logger; + private readonly IReadOnlyFileSystem _fileSystem; + + public SpellCheckCommand([FromKeyedServices("dictionaries")] IAssetSource dictionaries, + ILogger logger, + IReadOnlyFileSystem fileSystem) + { + _dictionaries = dictionaries; + _logger = logger; + _fileSystem = fileSystem; + } + + public override async Task ExecuteAsync(SpellCheckArguments arguments, IReadOnlyList context) + { + if (arguments.DictionariesDisplay) + { + _logger.LogInformation("Available dictionaries:"); + foreach (string assetName in _dictionaries.AssetNames) + { + if (assetName.EndsWith(".dic", StringComparison.OrdinalIgnoreCase)) + { + string language = Path.GetFileNameWithoutExtension(assetName); + _logger.LogInformation("- {Language}", language); + } + } + return ExitCodes.Success; + } + + WordList? dictionaries = await LoadDictionaries(arguments.Language); + if (dictionaries == null) + { + _logger.LogError("Dictionaries for language '{Language}' not found.", arguments.Language); + return ExitCodes.GeneralError; + } + + var extension = Path.GetExtension(arguments.InputFile); + + int count = 0; + + if (string.Equals(extension, ".txt", StringComparison.OrdinalIgnoreCase)) + { + string text = await _fileSystem.ReadAllTextAsync(arguments.InputFile); + foreach (var word in SplitIntoWords(text)) + { + if (!string.IsNullOrWhiteSpace(word) && !dictionaries.Check(word)) + { + IEnumerable suggestions = dictionaries.Suggest(word); + _logger.LogWarning("Misspelled word: '{Word}'. Suggestions: {Suggestions}", word, string.Join(", ", suggestions)); + ++count; + } + } + } + + if (string.Equals(extension, ".md", StringComparison.OrdinalIgnoreCase)) + { + string markdown = await _fileSystem.ReadAllTextAsync(arguments.InputFile); + var document = Markdig.Markdown.Parse(markdown); + CheckBlock(document, dictionaries, ref count); + } + + return count == 0 ? ExitCodes.Success : ExitCodes.GeneralError; + + } + + private async Task LoadDictionaries(string language) + { + string dicFileName = $"{language}.dic"; + string affFileName = $"{language}.aff"; + + if (!_dictionaries.AssetNames.Contains(dicFileName) + && !_dictionaries.AssetNames.Contains(affFileName)) + { + return null; + } + + await using Stream dicStream = _dictionaries.GetBinaryAssetStream(dicFileName); + await using Stream affStream = _dictionaries.GetBinaryAssetStream(affFileName); + return await WordList.CreateFromStreamsAsync(dicStream, affStream); + } + + private void CheckBlock(Block block, WordList spellChecker, ref int count) + { + if (block is LeafBlock leafBlock) + { + if (leafBlock.Inline != null) + { + foreach (Inline inline in leafBlock.Inline) + { + CheckInline(inline, spellChecker, ref count); + } + } + } + else if (block is ContainerBlock containerBlock) + { + foreach (Block child in containerBlock) + { + CheckBlock(child, spellChecker, ref count); + } + } + } + + private void CheckInline(Inline inline, WordList spellChecker, ref int count) + { + if (inline is LiteralInline literal) + { + IEnumerable words = SplitIntoWords(literal.Content.ToString()); + foreach (var word in words) + { + if (!string.IsNullOrWhiteSpace(word) && !spellChecker.Check(word)) + { + IEnumerable suggestions = spellChecker.Suggest(word); + _logger.LogWarning("Misspelled word: '{Word}'. Suggestions: {Suggestions}", word, string.Join(", ", suggestions)); + } + } + } + else if (inline is ContainerInline container) + { + foreach (Inline child in container) + { + CheckInline(child, spellChecker, ref count); + } + } + } + + private static IEnumerable SplitIntoWords(string text) + { + var currentWord = new StringBuilder(); + + foreach (char c in text) + { + if (char.IsLetter(c) || c == '\'') + { + currentWord.Append(c); + } + else if (currentWord.Length > 0) + { + string result = currentWord.ToString(); + currentWord.Clear(); + yield return result; + } + } + + if (currentWord.Length > 0) + { + yield return currentWord.ToString(); + } + } +} diff --git a/Source/BookGen/Program.cs b/Source/BookGen/Program.cs index 76b025ec..aa1a51d9 100644 --- a/Source/BookGen/Program.cs +++ b/Source/BookGen/Program.cs @@ -54,6 +54,15 @@ ioc.AddTransient(); ioc.AddTransient(); ioc.AddTransient(); +ioc.AddKeyedSingleton("dictionaries", (provider, key) => +{ + var dictionaries = Path.Combine(AppContext.BaseDirectory, "dictionaries.zip"); + if (File.Exists(dictionaries)) + { + return new ZipAssetSoruce(dictionaries); + } + return new EmptyAssetSource(); +}); using ServiceProvider provider = ioc.BuildServiceProvider(); diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index 3c5155a9..83e3e26d 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -61,6 +61,12 @@ "resolved": "1.0.0", "contentHash": "TojbwWASRf3PMUsotXa8MQ9SNwEwWZZ5bAYf1eLv+jiDsxaD/n7TKTi3da6z4+m8uZHri2i+BVRuLj5w+6j64Q==" }, + "WeCantSpell.Hunspell": { + "type": "Direct", + "requested": "[7.0.1, )", + "resolved": "7.0.1", + "contentHash": "Ha0x66QWA+IPj2kJYoweA8MSnB1tymBW6kAIS6Kkj/U3SlMLCBPjDlUXzrrY1zJAxVItrGpFIXjkl3eEIYIE9A==" + }, "XmlDocMarkdown.Core": { "type": "Direct", "requested": "[2.9.0, )", From f0d6b5183b1f6ee0e33b5923d6c7930a6badbfa2 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Thu, 26 Feb 2026 22:53:12 +0100 Subject: [PATCH 31/41] Publish script changes --- Assets/compile-dictionaries.ps1 | 5 +++ Installers/run_shell.cmd | 8 +++++ Source/BookGen.Cli/packages.lock.json | 3 +- .../BookGen.Shell.Shared/packages.lock.json | 3 +- Source/BookGen.Shellprog/packages.lock.json | 3 +- Source/BookGen/Commands/SpellCheckCommand.cs | 3 +- Source/BookGen/packages.lock.json | 36 +++++++++++++++++++ Source/Bookgen.Lib/packages.lock.json | 36 +++++++++++++++++++ publish.ps1 | 13 +++++-- 9 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 Installers/run_shell.cmd diff --git a/Assets/compile-dictionaries.ps1 b/Assets/compile-dictionaries.ps1 index ec0bc423..aa97f8b4 100644 --- a/Assets/compile-dictionaries.ps1 +++ b/Assets/compile-dictionaries.ps1 @@ -3,6 +3,11 @@ if (-not (Test-Path $dictionariesPath)) { New-Item -ItemType Directory -Path $dictionariesPath | Out-Null } +if (Test-Path "dictionaries.zip") { + Write-Host "dictionaries.zip already exists. Exiting script." + exit +} + cd $dictionariesPath curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.aff" curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.dic" diff --git a/Installers/run_shell.cmd b/Installers/run_shell.cmd new file mode 100644 index 00000000..c7eaf771 --- /dev/null +++ b/Installers/run_shell.cmd @@ -0,0 +1,8 @@ +@echo off +title "Bookgen shell start script" +where /q pwsh +if %errorlevel%==0 ( + pwsh -NoExit -File "%~dp0bin\BookGenShell.ps1" +) else ( + powershell -NoExit -File "%~dp0bin\BookGenShell.ps1" +) diff --git a/Source/BookGen.Cli/packages.lock.json b/Source/BookGen.Cli/packages.lock.json index f260753c..bbae18d1 100644 --- a/Source/BookGen.Cli/packages.lock.json +++ b/Source/BookGen.Cli/packages.lock.json @@ -20,6 +20,7 @@ "bookgen.vfs": { "type": "Project" } - } + }, + "net10.0/linux-x64": {} } } \ No newline at end of file diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json index 0bbd134f..f2a47d33 100644 --- a/Source/BookGen.Shell.Shared/packages.lock.json +++ b/Source/BookGen.Shell.Shared/packages.lock.json @@ -35,6 +35,7 @@ "resolved": "10.0.3", "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" } - } + }, + "net10.0/linux-x64": {} } } \ No newline at end of file diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json index 3fb26f16..a82a47ec 100644 --- a/Source/BookGen.Shellprog/packages.lock.json +++ b/Source/BookGen.Shellprog/packages.lock.json @@ -91,6 +91,7 @@ "resolved": "4.1.1", "contentHash": "0z1y6nFmpAhHP0x/aus62qIS+MDwntC8JYvkqEm5v9QyszzmyQG5Jp01a6FYacaXZ7CsvQgV7TzhA6G6p6MTSA==" } - } + }, + "net10.0/linux-x64": {} } } \ No newline at end of file diff --git a/Source/BookGen/Commands/SpellCheckCommand.cs b/Source/BookGen/Commands/SpellCheckCommand.cs index e7c3cb78..af7af400 100644 --- a/Source/BookGen/Commands/SpellCheckCommand.cs +++ b/Source/BookGen/Commands/SpellCheckCommand.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.Contracts; -using System.Text; +using System.Text; using BookGen.Cli; using BookGen.Cli.Annotations; diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index 83e3e26d..d3550ecf 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -280,6 +280,42 @@ "resolved": "16.3.0", "contentHash": "SgMOdxbz8X65z8hraIs6hOEdnkH6hESTAIUa7viEngHOYaH+6q5XJmwr1+yb9vJpNQ19hCQY69xbFsLtXpobQA==" } + }, + "net10.0/linux-x64": { + "Microsoft.ClearScript.V8.Native.win-x86": { + "type": "Transitive", + "resolved": "7.5.0", + "contentHash": "rBbkxbCM6D4Y7xrKPcf2XJ3aD9pNik1q64ZleR6pT+RJZRz/7aDbMmcCcDm29AUnu2QHmrSsPA3KpLZm5rmnFg==" + }, + "SkiaSharp.NativeAssets.macOS": { + "type": "Transitive", + "resolved": "3.119.2", + "contentHash": "I2jMGQ/26KOnc6iAoR+Mxh9vSJJ2vioJyj9aJ9OL5yEZyXothXJxf4vBMqnSaiXMqiiU1scG7KqtT0CLkmMmWA==" + }, + "Microsoft.ClearScript.V8.Native.linux-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" + }, + "Microsoft.ClearScript.V8.Native.win-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" + }, + "SkiaSharp.NativeAssets.Linux": { + "type": "CentralTransitive", + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "9WzxSyG/s9Id506j0Ht+Bi5ucOpWKPzd1XXr9TD4fuCafHHy2swRSlbZtC3IDQAsvCH63OerkDJajj43uSv5og==" + }, + "SkiaSharp.NativeAssets.Win32": { + "type": "CentralTransitive", + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "uYe+da6+GXVgPKkCopzvIZ83DmC8SXXKeUAPrNcztJNsg0SjPQAxfKMOPZqmVjbzznrq/QUIjLUlJSZV/e0IPA==" + } } } } \ No newline at end of file diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index 9f69682a..8e3d25a0 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -178,6 +178,42 @@ "resolved": "4.1.1", "contentHash": "0z1y6nFmpAhHP0x/aus62qIS+MDwntC8JYvkqEm5v9QyszzmyQG5Jp01a6FYacaXZ7CsvQgV7TzhA6G6p6MTSA==" } + }, + "net10.0/linux-x64": { + "Microsoft.ClearScript.V8.Native.linux-x64": { + "type": "Direct", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" + }, + "Microsoft.ClearScript.V8.Native.win-x64": { + "type": "Direct", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" + }, + "SkiaSharp.NativeAssets.Linux": { + "type": "Direct", + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "9WzxSyG/s9Id506j0Ht+Bi5ucOpWKPzd1XXr9TD4fuCafHHy2swRSlbZtC3IDQAsvCH63OerkDJajj43uSv5og==" + }, + "SkiaSharp.NativeAssets.Win32": { + "type": "Direct", + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "uYe+da6+GXVgPKkCopzvIZ83DmC8SXXKeUAPrNcztJNsg0SjPQAxfKMOPZqmVjbzznrq/QUIjLUlJSZV/e0IPA==" + }, + "Microsoft.ClearScript.V8.Native.win-x86": { + "type": "Transitive", + "resolved": "7.5.0", + "contentHash": "rBbkxbCM6D4Y7xrKPcf2XJ3aD9pNik1q64ZleR6pT+RJZRz/7aDbMmcCcDm29AUnu2QHmrSsPA3KpLZm5rmnFg==" + }, + "SkiaSharp.NativeAssets.macOS": { + "type": "Transitive", + "resolved": "3.119.2", + "contentHash": "I2jMGQ/26KOnc6iAoR+Mxh9vSJJ2vioJyj9aJ9OL5yEZyXothXJxf4vBMqnSaiXMqiiU1scG7KqtT0CLkmMmWA==" + } } } } \ No newline at end of file diff --git a/publish.ps1 b/publish.ps1 index f67a2c75..39959a44 100644 --- a/publish.ps1 +++ b/publish.ps1 @@ -27,6 +27,7 @@ function Invoke-Publish { # copy installer scripts Copy-Item "Installers\install.cmd" "bin\publish\windows\install.cmd" + Copy-Item "Installers\run_shell.cmd" "bin\publish\linux\run_shell.cmd" # copy assets Copy-Item "bin\Release\assets.zip" "bin\publish\windows\bin\assets.zip" @@ -49,15 +50,21 @@ function Invoke-Publish { # zip if ($SelfContained) { Compress-Archive -Path "bin\publish\windows\*" -DestinationPath "bin\publish\$WindowsArchiveName" -Force + clear tar -czvf "bin\publish\$LinuxArchiveName" -C "bin\publish\linux" . } else { Compress-Archive -Path "bin\publish\windows\*" -DestinationPath "bin\publish\$WindowsArchiveName" -Force + clear tar -czvf "bin\publish\$LinuxArchiveName" -C "bin\publish\linux" . } } +cd assets +.\compile-dictionaries.ps1 +cd .. -# Self-contained build and archives -Invoke-Publish -SelfContained $true -WindowsArchiveName "BookGen-windows-selefcontained.zip" -LinuxArchiveName "BookGen-linux-selefcontained.tar.gz" # Framework-dependent build and archives -Invoke-Publish -SelfContained $false -WindowsArchiveName "BookGen-windows.zip" -LinuxArchiveName "BookGen-linux.tar.gz" \ No newline at end of file +Invoke-Publish -SelfContained $false -WindowsArchiveName "BookGen-windows.zip" -LinuxArchiveName "BookGen-linux.tar.gz" + +# Self-contained build and archives +Invoke-Publish -SelfContained $true -WindowsArchiveName "BookGen-windows-selefcontained.zip" -LinuxArchiveName "BookGen-linux-selefcontained.tar.gz" \ No newline at end of file From 7b1e59aca91e156466adf2688f4c3c2d5c040dae Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sat, 28 Feb 2026 18:21:32 +0100 Subject: [PATCH 32/41] Dictionaries handling modified --- Assets/.gitignore | 3 - Assets/compile-dictionaries.ps1 | 30 - Assets/dictionaries/af_ZA.aff | 1485 +++++++++++++++++ Assets/dictionaries/af_ZA.dic | 1485 +++++++++++++++++ Assets/dictionaries/de_DE_frami.aff | 1485 +++++++++++++++++ Assets/dictionaries/de_DE_frami.dic | 1485 +++++++++++++++++ Assets/dictionaries/en_GB.aff | 1485 +++++++++++++++++ Assets/dictionaries/en_GB.dic | 1485 +++++++++++++++++ Assets/dictionaries/en_US.aff | 1485 +++++++++++++++++ Assets/dictionaries/en_US.dic | 1485 +++++++++++++++++ Assets/dictionaries/es_ES.aff | 1485 +++++++++++++++++ Assets/dictionaries/es_ES.dic | 1485 +++++++++++++++++ Assets/dictionaries/hr_HR.aff | 1485 +++++++++++++++++ Assets/dictionaries/hr_HR.dic | 1485 +++++++++++++++++ Assets/dictionaries/hu_HU.aff | 1485 +++++++++++++++++ Assets/dictionaries/hu_HU.dic | 1485 +++++++++++++++++ Assets/dictionaries/it_IT.aff | 1485 +++++++++++++++++ Assets/dictionaries/it_IT.dic | 1485 +++++++++++++++++ Assets/dictionaries/nl_NL.aff | 1485 +++++++++++++++++ Assets/dictionaries/nl_NL.dic | 1485 +++++++++++++++++ Assets/download-dictionaries.ps1 | 24 + Installers/install.cmd | 8 - Installers/run_shell.cmd | 8 - Source/BookGen.Cli/packages.lock.json | 3 +- .../BookGen.Contents/BookGen.Contents.csproj | 21 +- .../BookGen.Shell.Shared/packages.lock.json | 3 +- Source/BookGen.Shellprog/packages.lock.json | 3 +- Source/BookGen/packages.lock.json | 36 - Source/Bookgen.Lib/packages.lock.json | 36 - publish.ps1 | 11 +- 30 files changed, 26780 insertions(+), 136 deletions(-) delete mode 100644 Assets/.gitignore delete mode 100644 Assets/compile-dictionaries.ps1 create mode 100644 Assets/dictionaries/af_ZA.aff create mode 100644 Assets/dictionaries/af_ZA.dic create mode 100644 Assets/dictionaries/de_DE_frami.aff create mode 100644 Assets/dictionaries/de_DE_frami.dic create mode 100644 Assets/dictionaries/en_GB.aff create mode 100644 Assets/dictionaries/en_GB.dic create mode 100644 Assets/dictionaries/en_US.aff create mode 100644 Assets/dictionaries/en_US.dic create mode 100644 Assets/dictionaries/es_ES.aff create mode 100644 Assets/dictionaries/es_ES.dic create mode 100644 Assets/dictionaries/hr_HR.aff create mode 100644 Assets/dictionaries/hr_HR.dic create mode 100644 Assets/dictionaries/hu_HU.aff create mode 100644 Assets/dictionaries/hu_HU.dic create mode 100644 Assets/dictionaries/it_IT.aff create mode 100644 Assets/dictionaries/it_IT.dic create mode 100644 Assets/dictionaries/nl_NL.aff create mode 100644 Assets/dictionaries/nl_NL.dic create mode 100644 Assets/download-dictionaries.ps1 delete mode 100644 Installers/install.cmd delete mode 100644 Installers/run_shell.cmd diff --git a/Assets/.gitignore b/Assets/.gitignore deleted file mode 100644 index fba3ac75..00000000 --- a/Assets/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -dictionaries/*.dic -dictionaries/*.aff -dictionaries.zip \ No newline at end of file diff --git a/Assets/compile-dictionaries.ps1 b/Assets/compile-dictionaries.ps1 deleted file mode 100644 index aa97f8b4..00000000 --- a/Assets/compile-dictionaries.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -$dictionariesPath = ".\dictionaries" -if (-not (Test-Path $dictionariesPath)) { - New-Item -ItemType Directory -Path $dictionariesPath | Out-Null -} - -if (Test-Path "dictionaries.zip") { - Write-Host "dictionaries.zip already exists. Exiting script." - exit -} - -cd $dictionariesPath -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.aff" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.dic" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_US.aff" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_US.dic" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hu_HU/hu_HU.aff" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hu_HU/hu_HU.dic" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hr_HR/hr_HR.aff" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hr_HR/hr_HR.dic" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/it_IT/it_IT.aff" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/it_IT/it_IT.dic" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/nl_NL/nl_NL.aff" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/nl_NL/nl_NL.dic" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/de/de_DE_frami.aff" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/de/de_DE_frami.dic" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/es/es_ES.aff" -curl -L -O "https://github.com/LibreOffice/dictionaries/blob/master/es/es_ES.dic" -cd .. - -Compress-Archive -Path "$dictionariesPath\*" -DestinationPath "dictionaries.zip" -CompressionLevel Optimal diff --git a/Assets/dictionaries/af_ZA.aff b/Assets/dictionaries/af_ZA.aff new file mode 100644 index 00000000..0759c265 --- /dev/null +++ b/Assets/dictionaries/af_ZA.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/af_ZA/af_ZA.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/af_ZA.dic b/Assets/dictionaries/af_ZA.dic new file mode 100644 index 00000000..c7c58134 --- /dev/null +++ b/Assets/dictionaries/af_ZA.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/af_ZA/af_ZA.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/de_DE_frami.aff b/Assets/dictionaries/de_DE_frami.aff new file mode 100644 index 00000000..528e2f7e --- /dev/null +++ b/Assets/dictionaries/de_DE_frami.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/de/de_DE_frami.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/de_DE_frami.dic b/Assets/dictionaries/de_DE_frami.dic new file mode 100644 index 00000000..6bc205d2 --- /dev/null +++ b/Assets/dictionaries/de_DE_frami.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/de/de_DE_frami.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/en_GB.aff b/Assets/dictionaries/en_GB.aff new file mode 100644 index 00000000..2e825980 --- /dev/null +++ b/Assets/dictionaries/en_GB.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/en/en_GB.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/en_GB.dic b/Assets/dictionaries/en_GB.dic new file mode 100644 index 00000000..f99aa9f3 --- /dev/null +++ b/Assets/dictionaries/en_GB.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/en/en_GB.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/en_US.aff b/Assets/dictionaries/en_US.aff new file mode 100644 index 00000000..f7d5f826 --- /dev/null +++ b/Assets/dictionaries/en_US.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/en/en_US.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/en_US.dic b/Assets/dictionaries/en_US.dic new file mode 100644 index 00000000..2a2d405d --- /dev/null +++ b/Assets/dictionaries/en_US.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/en/en_US.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/es_ES.aff b/Assets/dictionaries/es_ES.aff new file mode 100644 index 00000000..6eebee96 --- /dev/null +++ b/Assets/dictionaries/es_ES.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/es/es_ES.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/es_ES.dic b/Assets/dictionaries/es_ES.dic new file mode 100644 index 00000000..a1858be5 --- /dev/null +++ b/Assets/dictionaries/es_ES.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/es/es_ES.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/hr_HR.aff b/Assets/dictionaries/hr_HR.aff new file mode 100644 index 00000000..e3511130 --- /dev/null +++ b/Assets/dictionaries/hr_HR.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/hr_HR/hr_HR.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/hr_HR.dic b/Assets/dictionaries/hr_HR.dic new file mode 100644 index 00000000..797f4a63 --- /dev/null +++ b/Assets/dictionaries/hr_HR.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/hr_HR/hr_HR.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/hu_HU.aff b/Assets/dictionaries/hu_HU.aff new file mode 100644 index 00000000..cb831357 --- /dev/null +++ b/Assets/dictionaries/hu_HU.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/hu_HU/hu_HU.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/hu_HU.dic b/Assets/dictionaries/hu_HU.dic new file mode 100644 index 00000000..7692e93a --- /dev/null +++ b/Assets/dictionaries/hu_HU.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/hu_HU/hu_HU.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/it_IT.aff b/Assets/dictionaries/it_IT.aff new file mode 100644 index 00000000..c44a6359 --- /dev/null +++ b/Assets/dictionaries/it_IT.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/it_IT/it_IT.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/it_IT.dic b/Assets/dictionaries/it_IT.dic new file mode 100644 index 00000000..53d44c25 --- /dev/null +++ b/Assets/dictionaries/it_IT.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/it_IT/it_IT.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/nl_NL.aff b/Assets/dictionaries/nl_NL.aff new file mode 100644 index 00000000..e64fb30d --- /dev/null +++ b/Assets/dictionaries/nl_NL.aff @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/nl_NL/nl_NL.aff at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/dictionaries/nl_NL.dic b/Assets/dictionaries/nl_NL.dic new file mode 100644 index 00000000..6d5d5272 --- /dev/null +++ b/Assets/dictionaries/nl_NL.dic @@ -0,0 +1,1485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dictionaries/nl_NL/nl_NL.dic at master · LibreOffice/dictionaries · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + Skip to content + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + +
    + + + + + +
    + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + / + + dictionaries + + + Public +
    + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +

    Footer

    + + + + +
    +
    + + + + + © 2026 GitHub, Inc. + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/Assets/download-dictionaries.ps1 b/Assets/download-dictionaries.ps1 new file mode 100644 index 00000000..e275c4a7 --- /dev/null +++ b/Assets/download-dictionaries.ps1 @@ -0,0 +1,24 @@ +$dictionariesPath = ".\dictionaries" +if (-not (Test-Path $dictionariesPath)) { + New-Item -ItemType Directory -Path $dictionariesPath | Out-Null +} + +cd $dictionariesPath +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.aff" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_GB.dic" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_US.aff" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/en/en_US.dic" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hu_HU/hu_HU.aff" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hu_HU/hu_HU.dic" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hr_HR/hr_HR.aff" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/hr_HR/hr_HR.dic" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/it_IT/it_IT.aff" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/it_IT/it_IT.dic" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/nl_NL/nl_NL.aff" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/nl_NL/nl_NL.dic" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/de/de_DE_frami.aff" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/de/de_DE_frami.dic" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/es/es_ES.aff" +curl.exe -L -O "https://github.com/LibreOffice/dictionaries/blob/master/es/es_ES.dic" +cd .. + diff --git a/Installers/install.cmd b/Installers/install.cmd deleted file mode 100644 index 3aa0c055..00000000 --- a/Installers/install.cmd +++ /dev/null @@ -1,8 +0,0 @@ -@echo off -title "Bookgen Install script" - -bin\bookgen.exe install - -echo "Bookgen install complete" -pause -exit diff --git a/Installers/run_shell.cmd b/Installers/run_shell.cmd deleted file mode 100644 index c7eaf771..00000000 --- a/Installers/run_shell.cmd +++ /dev/null @@ -1,8 +0,0 @@ -@echo off -title "Bookgen shell start script" -where /q pwsh -if %errorlevel%==0 ( - pwsh -NoExit -File "%~dp0bin\BookGenShell.ps1" -) else ( - powershell -NoExit -File "%~dp0bin\BookGenShell.ps1" -) diff --git a/Source/BookGen.Cli/packages.lock.json b/Source/BookGen.Cli/packages.lock.json index bbae18d1..f260753c 100644 --- a/Source/BookGen.Cli/packages.lock.json +++ b/Source/BookGen.Cli/packages.lock.json @@ -20,7 +20,6 @@ "bookgen.vfs": { "type": "Project" } - }, - "net10.0/linux-x64": {} + } } } \ No newline at end of file diff --git a/Source/BookGen.Contents/BookGen.Contents.csproj b/Source/BookGen.Contents/BookGen.Contents.csproj index 287e4ea8..28bddcf7 100644 --- a/Source/BookGen.Contents/BookGen.Contents.csproj +++ b/Source/BookGen.Contents/BookGen.Contents.csproj @@ -15,9 +15,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -38,6 +35,15 @@ + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..\..\Assets\dictionaries')) + $([System.IO.Path]::GetFullPath('$(OutputPath)\dictionaries.zip')) + + + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/../../Assets/BundledAssets')) @@ -46,5 +52,14 @@ + + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/../../Assets/dictionaries')) + $([System.IO.Path]::GetFullPath('$(OutputPath)/dictionaries.zip')) + + + + diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json index f2a47d33..0bbd134f 100644 --- a/Source/BookGen.Shell.Shared/packages.lock.json +++ b/Source/BookGen.Shell.Shared/packages.lock.json @@ -35,7 +35,6 @@ "resolved": "10.0.3", "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" } - }, - "net10.0/linux-x64": {} + } } } \ No newline at end of file diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json index a82a47ec..3fb26f16 100644 --- a/Source/BookGen.Shellprog/packages.lock.json +++ b/Source/BookGen.Shellprog/packages.lock.json @@ -91,7 +91,6 @@ "resolved": "4.1.1", "contentHash": "0z1y6nFmpAhHP0x/aus62qIS+MDwntC8JYvkqEm5v9QyszzmyQG5Jp01a6FYacaXZ7CsvQgV7TzhA6G6p6MTSA==" } - }, - "net10.0/linux-x64": {} + } } } \ No newline at end of file diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index d3550ecf..83e3e26d 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -280,42 +280,6 @@ "resolved": "16.3.0", "contentHash": "SgMOdxbz8X65z8hraIs6hOEdnkH6hESTAIUa7viEngHOYaH+6q5XJmwr1+yb9vJpNQ19hCQY69xbFsLtXpobQA==" } - }, - "net10.0/linux-x64": { - "Microsoft.ClearScript.V8.Native.win-x86": { - "type": "Transitive", - "resolved": "7.5.0", - "contentHash": "rBbkxbCM6D4Y7xrKPcf2XJ3aD9pNik1q64ZleR6pT+RJZRz/7aDbMmcCcDm29AUnu2QHmrSsPA3KpLZm5rmnFg==" - }, - "SkiaSharp.NativeAssets.macOS": { - "type": "Transitive", - "resolved": "3.119.2", - "contentHash": "I2jMGQ/26KOnc6iAoR+Mxh9vSJJ2vioJyj9aJ9OL5yEZyXothXJxf4vBMqnSaiXMqiiU1scG7KqtT0CLkmMmWA==" - }, - "Microsoft.ClearScript.V8.Native.linux-x64": { - "type": "CentralTransitive", - "requested": "[7.5.0, )", - "resolved": "7.5.0", - "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" - }, - "Microsoft.ClearScript.V8.Native.win-x64": { - "type": "CentralTransitive", - "requested": "[7.5.0, )", - "resolved": "7.5.0", - "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" - }, - "SkiaSharp.NativeAssets.Linux": { - "type": "CentralTransitive", - "requested": "[3.119.2, )", - "resolved": "3.119.2", - "contentHash": "9WzxSyG/s9Id506j0Ht+Bi5ucOpWKPzd1XXr9TD4fuCafHHy2swRSlbZtC3IDQAsvCH63OerkDJajj43uSv5og==" - }, - "SkiaSharp.NativeAssets.Win32": { - "type": "CentralTransitive", - "requested": "[3.119.2, )", - "resolved": "3.119.2", - "contentHash": "uYe+da6+GXVgPKkCopzvIZ83DmC8SXXKeUAPrNcztJNsg0SjPQAxfKMOPZqmVjbzznrq/QUIjLUlJSZV/e0IPA==" - } } } } \ No newline at end of file diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index 8e3d25a0..9f69682a 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -178,42 +178,6 @@ "resolved": "4.1.1", "contentHash": "0z1y6nFmpAhHP0x/aus62qIS+MDwntC8JYvkqEm5v9QyszzmyQG5Jp01a6FYacaXZ7CsvQgV7TzhA6G6p6MTSA==" } - }, - "net10.0/linux-x64": { - "Microsoft.ClearScript.V8.Native.linux-x64": { - "type": "Direct", - "requested": "[7.5.0, )", - "resolved": "7.5.0", - "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" - }, - "Microsoft.ClearScript.V8.Native.win-x64": { - "type": "Direct", - "requested": "[7.5.0, )", - "resolved": "7.5.0", - "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" - }, - "SkiaSharp.NativeAssets.Linux": { - "type": "Direct", - "requested": "[3.119.2, )", - "resolved": "3.119.2", - "contentHash": "9WzxSyG/s9Id506j0Ht+Bi5ucOpWKPzd1XXr9TD4fuCafHHy2swRSlbZtC3IDQAsvCH63OerkDJajj43uSv5og==" - }, - "SkiaSharp.NativeAssets.Win32": { - "type": "Direct", - "requested": "[3.119.2, )", - "resolved": "3.119.2", - "contentHash": "uYe+da6+GXVgPKkCopzvIZ83DmC8SXXKeUAPrNcztJNsg0SjPQAxfKMOPZqmVjbzznrq/QUIjLUlJSZV/e0IPA==" - }, - "Microsoft.ClearScript.V8.Native.win-x86": { - "type": "Transitive", - "resolved": "7.5.0", - "contentHash": "rBbkxbCM6D4Y7xrKPcf2XJ3aD9pNik1q64ZleR6pT+RJZRz/7aDbMmcCcDm29AUnu2QHmrSsPA3KpLZm5rmnFg==" - }, - "SkiaSharp.NativeAssets.macOS": { - "type": "Transitive", - "resolved": "3.119.2", - "contentHash": "I2jMGQ/26KOnc6iAoR+Mxh9vSJJ2vioJyj9aJ9OL5yEZyXothXJxf4vBMqnSaiXMqiiU1scG7KqtT0CLkmMmWA==" - } } } } \ No newline at end of file diff --git a/publish.ps1 b/publish.ps1 index 39959a44..5e2f66c4 100644 --- a/publish.ps1 +++ b/publish.ps1 @@ -26,8 +26,8 @@ function Invoke-Publish { } # copy installer scripts - Copy-Item "Installers\install.cmd" "bin\publish\windows\install.cmd" - Copy-Item "Installers\run_shell.cmd" "bin\publish\linux\run_shell.cmd" + Copy-Item "Publish\install.cmd" "bin\publish\windows\install.cmd" + Copy-Item "Publish\run_shell.cmd" "bin\publish\windows\run_shell.cmd" # copy assets Copy-Item "bin\Release\assets.zip" "bin\publish\windows\bin\assets.zip" @@ -59,12 +59,11 @@ function Invoke-Publish { tar -czvf "bin\publish\$LinuxArchiveName" -C "bin\publish\linux" . } } -cd assets -.\compile-dictionaries.ps1 -cd .. # Framework-dependent build and archives Invoke-Publish -SelfContained $false -WindowsArchiveName "BookGen-windows.zip" -LinuxArchiveName "BookGen-linux.tar.gz" # Self-contained build and archives -Invoke-Publish -SelfContained $true -WindowsArchiveName "BookGen-windows-selefcontained.zip" -LinuxArchiveName "BookGen-linux-selefcontained.tar.gz" \ No newline at end of file +Invoke-Publish -SelfContained $true -WindowsArchiveName "BookGen-windows-selefcontained.zip" -LinuxArchiveName "BookGen-linux-selefcontained.tar.gz" + +.\Publish\mkisofs.exe -V BookGen -o .\bin\publish\bookgen-windows.iso -udf .\bin\publish\windows \ No newline at end of file From a1f538160bb7a76cbd9d76beb43c99ff57987c7f Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sun, 1 Mar 2026 12:23:56 +0100 Subject: [PATCH 33/41] Rework caching and JS interop for syntax highlighting Prepare for nomnoml rendering --- Directory.Packages.props | 2 + Source/BookGen/BookGen.csproj | 2 + Source/BookGen/BuildCommandBase.cs | 8 +- Source/BookGen/Commands/BuildEpub.cs | 10 ++- Source/BookGen/Commands/BuildExportCommand.cs | 14 ++-- Source/BookGen/Commands/BuildFeedCommand.cs | 10 ++- Source/BookGen/Commands/BuildPrintCommand.cs | 10 ++- Source/BookGen/Commands/BuildWebCommand.cs | 10 ++- .../BookGen/Commands/BuildWordpressCommand.cs | 10 ++- Source/BookGen/Commands/Md2HtmlCommand.cs | 4 +- Source/BookGen/Program.cs | 1 + Source/BookGen/packages.lock.json | 22 ++++++ Source/Bookgen.Lib/Bookgen.Lib.csproj | 1 + .../ImageService/CachedImageService.cs | 50 +++++++++--- .../Bookgen.Lib/ImageService/IImgService.cs | 5 +- .../ImageService/ImageConverter.cs | 6 +- .../ImageService/{Utils.cs => ImageUtils.cs} | 32 ++++++-- Source/Bookgen.Lib/ImageService/ImgService.cs | 78 +++++++++++++------ ...mJsInterop.cs => SyntaxRenderJsInterop.cs} | 18 +++-- Source/Bookgen.Lib/Markdown/RenderSettings.cs | 2 +- .../Markdown/Renderers/SyntaxRenderer.cs | 6 +- .../Pipeline/Epub/CreateEpubCoverAndStyle.cs | 4 +- .../Pipeline/Epub/CreateHtmlPages.cs | 12 ++- .../Bookgen.Lib/Pipeline/Feed/CreateItems.cs | 12 ++- Source/Bookgen.Lib/Pipeline/Pipeline.cs | 29 +++---- .../PostProcess/RenderPagesForPostProcess.cs | 14 +++- .../Bookgen.Lib/Pipeline/Print/RenderPages.cs | 12 ++- .../Pipeline/StaticWebsite/RenderIndexPage.cs | 10 ++- .../StaticWebsite/RenderStaticPages.cs | 10 ++- .../Pipeline/Wordpress/CreateWpPages.cs | 9 ++- Source/Bookgen.Lib/packages.lock.json | 6 ++ .../Bookgen.Tests/Lib/UT_MarkdownConverter.cs | 4 +- 32 files changed, 297 insertions(+), 126 deletions(-) rename Source/Bookgen.Lib/ImageService/{Utils.cs => ImageUtils.cs} (86%) rename Source/Bookgen.Lib/JsInterop/{PrismJsInterop.cs => SyntaxRenderJsInterop.cs} (53%) diff --git a/Directory.Packages.props b/Directory.Packages.props index ebcdc946..1a203868 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,6 +9,8 @@ + + diff --git a/Source/BookGen/BookGen.csproj b/Source/BookGen/BookGen.csproj index a9a7fab4..6cf6b380 100644 --- a/Source/BookGen/BookGen.csproj +++ b/Source/BookGen/BookGen.csproj @@ -23,6 +23,8 @@ + + diff --git a/Source/BookGen/BuildCommandBase.cs b/Source/BookGen/BuildCommandBase.cs index c0f4e1d8..df36b1c7 100644 --- a/Source/BookGen/BuildCommandBase.cs +++ b/Source/BookGen/BuildCommandBase.cs @@ -10,6 +10,7 @@ using BookGen.Infrastructure.Loging; using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace BookGen; @@ -19,17 +20,20 @@ internal abstract class BuildCommandBase : AsyncCommand protected readonly IWritableFileSystem _soruce; protected readonly IWritableFileSystem _target; protected readonly ILogger _logger; - private readonly IAssetSource _assetSource; + protected readonly IAssetSource _assetSource; + protected readonly IMemoryCache _memoryCache; public BuildCommandBase(IWritableFileSystem soruce, IWritableFileSystem target, ILogger logger, - IAssetSource assetSource) + IAssetSource assetSource, + IMemoryCache memoryCache) { _soruce = soruce; _target = target; _logger = logger; _assetSource = assetSource; + _memoryCache = memoryCache; } public abstract Pipeline GetPipeLine(); diff --git a/Source/BookGen/Commands/BuildEpub.cs b/Source/BookGen/Commands/BuildEpub.cs index cf0a93ce..d6ed8c41 100644 --- a/Source/BookGen/Commands/BuildEpub.cs +++ b/Source/BookGen/Commands/BuildEpub.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -8,6 +8,7 @@ using BookGen.Cli.Annotations; using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace BookGen.Commands; @@ -18,11 +19,12 @@ internal sealed class BuildEpub : BuildCommandBase public BuildEpub(IWritableFileSystem soruce, IWritableFileSystem target, ILogger logger, - IAssetSource assetSource) - : base(soruce, target, logger, assetSource) + IAssetSource assetSource, + IMemoryCache memoryCache) + : base(soruce, target, logger, assetSource, memoryCache) { } public override Pipeline GetPipeLine() - => Pipeline.CreateEpubPileLine(); + => Pipeline.CreateEpubPileLine(_memoryCache); } diff --git a/Source/BookGen/Commands/BuildExportCommand.cs b/Source/BookGen/Commands/BuildExportCommand.cs index ccaf89a5..1a71f562 100644 --- a/Source/BookGen/Commands/BuildExportCommand.cs +++ b/Source/BookGen/Commands/BuildExportCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -8,6 +8,7 @@ using BookGen.Cli.Annotations; using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace BookGen.Commands; @@ -16,13 +17,14 @@ namespace BookGen.Commands; internal sealed class BuildExportCommand : BuildCommandBase { public BuildExportCommand(IWritableFileSystem soruce, - IWritableFileSystem target, - ILogger logger, - IAssetSource assetSource) - : base(soruce, target, logger, assetSource) + IWritableFileSystem target, + ILogger logger, + IAssetSource assetSource, + IMemoryCache memoryCache) + : base(soruce, target, logger, assetSource, memoryCache) { } public override Pipeline GetPipeLine() - => Pipeline.CreatePostProcessPipeLine(); + => Pipeline.CreatePostProcessPipeLine(_memoryCache); } diff --git a/Source/BookGen/Commands/BuildFeedCommand.cs b/Source/BookGen/Commands/BuildFeedCommand.cs index b817deca..77941138 100644 --- a/Source/BookGen/Commands/BuildFeedCommand.cs +++ b/Source/BookGen/Commands/BuildFeedCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -8,6 +8,7 @@ using BookGen.Cli.Annotations; using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace BookGen.Commands; @@ -18,11 +19,12 @@ internal sealed class BuildFeedCommand : BuildCommandBase public BuildFeedCommand(IWritableFileSystem soruce, IWritableFileSystem target, ILogger logger, - IAssetSource assetSource) - : base(soruce, target, logger, assetSource) + IAssetSource assetSource, + IMemoryCache memoryCache) + : base(soruce, target, logger, assetSource, memoryCache) { } public override Pipeline GetPipeLine() - => Pipeline.CreateFeedPipeline(); + => Pipeline.CreateFeedPipeline(_memoryCache); } diff --git a/Source/BookGen/Commands/BuildPrintCommand.cs b/Source/BookGen/Commands/BuildPrintCommand.cs index 3b1a2f13..fb1aec67 100644 --- a/Source/BookGen/Commands/BuildPrintCommand.cs +++ b/Source/BookGen/Commands/BuildPrintCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -8,6 +8,7 @@ using BookGen.Cli.Annotations; using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace BookGen.Commands; @@ -18,11 +19,12 @@ internal sealed class BuildPrintCommand : BuildCommandBase public BuildPrintCommand(IWritableFileSystem soruce, IWritableFileSystem target, ILogger logger, - IAssetSource assetSource) - : base(soruce, target, logger, assetSource) + IAssetSource assetSource, + IMemoryCache memoryCache) + : base(soruce, target, logger, assetSource, memoryCache) { } public override Pipeline GetPipeLine() - => Pipeline.CratePrintPipeLine(); + => Pipeline.CratePrintPipeLine(_memoryCache); } diff --git a/Source/BookGen/Commands/BuildWebCommand.cs b/Source/BookGen/Commands/BuildWebCommand.cs index c2465d88..ef3cc040 100644 --- a/Source/BookGen/Commands/BuildWebCommand.cs +++ b/Source/BookGen/Commands/BuildWebCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -8,6 +8,7 @@ using BookGen.Cli.Annotations; using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace BookGen.Commands; @@ -18,11 +19,12 @@ internal sealed class BuildWebCommand : BuildCommandBase public BuildWebCommand(IWritableFileSystem soruce, IWritableFileSystem target, ILogger logger, - IAssetSource assetSource) - : base(soruce, target, logger, assetSource) + IAssetSource assetSource, + IMemoryCache memoryCache) + : base(soruce, target, logger, assetSource, memoryCache) { } public override Pipeline GetPipeLine() - => Pipeline.CreateWebPipeLine(); + => Pipeline.CreateWebPipeLine(_memoryCache); } diff --git a/Source/BookGen/Commands/BuildWordpressCommand.cs b/Source/BookGen/Commands/BuildWordpressCommand.cs index a01b575b..c8f2ec59 100644 --- a/Source/BookGen/Commands/BuildWordpressCommand.cs +++ b/Source/BookGen/Commands/BuildWordpressCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -8,6 +8,7 @@ using BookGen.Cli.Annotations; using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace BookGen.Commands; @@ -18,11 +19,12 @@ internal sealed class BuildWordpressCommand : BuildCommandBase public BuildWordpressCommand(IWritableFileSystem soruce, IWritableFileSystem target, ILogger logger, - IAssetSource assetSource) - : base(soruce, target, logger, assetSource) + IAssetSource assetSource, + IMemoryCache memoryCache) + : base(soruce, target, logger, assetSource, memoryCache) { } public override Pipeline GetPipeLine() - => Pipeline.CreateWordpressPipeLine(); + => Pipeline.CreateWordpressPipeLine(_memoryCache); } diff --git a/Source/BookGen/Commands/Md2HtmlCommand.cs b/Source/BookGen/Commands/Md2HtmlCommand.cs index daa1a210..03753878 100644 --- a/Source/BookGen/Commands/Md2HtmlCommand.cs +++ b/Source/BookGen/Commands/Md2HtmlCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -136,7 +136,7 @@ public override int Execute(Md2HtmlArguments arguments, IReadOnlyList co CssClasses = new CssClasses(), OffsetHeadingsBy = 0, AutoEmbedSupportedLinks = !arguments.NoEmbed, - PrismJsInterop = arguments.NoSyntax ? null : new PrismJsInterop(_assetSource) + PrismJsInterop = arguments.NoSyntax ? null : new SyntaxRenderJsInterop(_assetSource) }; using var markdownConverter = new MarkdownConverter(settings); diff --git a/Source/BookGen/Program.cs b/Source/BookGen/Program.cs index aa1a51d9..a7c28fd0 100644 --- a/Source/BookGen/Program.cs +++ b/Source/BookGen/Program.cs @@ -45,6 +45,7 @@ CommandRunnerProxy runnerProxy = new(); var ioc = new ServiceCollection(); +ioc.AddMemoryCache(); ioc.AddSingleton(logger); ioc.AddSingleton(info); ioc.AddSingleton(runnerProxy); diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index 83e3e26d..941e6f74 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -8,6 +8,28 @@ "resolved": "0.45.0", "contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg==" }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Direct", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.3" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Direct", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" + } + }, "Microsoft.Extensions.DependencyInjection": { "type": "Direct", "requested": "[10.0.3, )", diff --git a/Source/Bookgen.Lib/Bookgen.Lib.csproj b/Source/Bookgen.Lib/Bookgen.Lib.csproj index 69f1c965..f04d0847 100644 --- a/Source/Bookgen.Lib/Bookgen.Lib.csproj +++ b/Source/Bookgen.Lib/Bookgen.Lib.csproj @@ -27,6 +27,7 @@ + diff --git a/Source/Bookgen.Lib/ImageService/CachedImageService.cs b/Source/Bookgen.Lib/ImageService/CachedImageService.cs index 085192e6..39b5bf31 100644 --- a/Source/Bookgen.Lib/ImageService/CachedImageService.cs +++ b/Source/Bookgen.Lib/ImageService/CachedImageService.cs @@ -1,32 +1,64 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- using System.Collections.Concurrent; +using System.IO; using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; + namespace Bookgen.Lib.ImageService; public sealed class CachedImageService : IImgService { private readonly IImgService _service; - private readonly ConcurrentDictionary _cache; + private readonly IMemoryCache _memoryCache; - public CachedImageService(IImgService service) + public CachedImageService(IImgService service, IMemoryCache memoryCache) { _service = service; - _cache = new ConcurrentDictionary(); + _memoryCache = memoryCache; + } + + private static ulong GetCacheKey(string str) + { + const ulong ofset = 0xcbf29ce484222325; + const ulong prime = 0x00000100000001b3; + + ulong hash = ofset; + + foreach (char c in str) + { + hash ^= c; + hash *= prime; + } + + return hash; + } + + + public ImageResult EncodeSvg(string svgData) + { + ulong cacheKey = GetCacheKey(svgData); + return _memoryCache.GetOrCreate(cacheKey, entry => + { + + entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(180)); + return _service.EncodeSvg(svgData); + })!; } public ImageResult GetImageEmbedData(string path) { - if (_cache.TryGetValue(path, out ImageResult? data)) - return data; + ulong cacheKey = GetCacheKey(path); + return _memoryCache.GetOrCreate(cacheKey, entry => + { - ImageResult result = _service.GetImageEmbedData(path); - _cache.TryAdd(path, result); - return result; + entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(180)); + return _service.GetImageEmbedData(path); + })!; } } diff --git a/Source/Bookgen.Lib/ImageService/IImgService.cs b/Source/Bookgen.Lib/ImageService/IImgService.cs index 0d39ee6a..4717f45e 100644 --- a/Source/Bookgen.Lib/ImageService/IImgService.cs +++ b/Source/Bookgen.Lib/ImageService/IImgService.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -7,5 +7,6 @@ namespace Bookgen.Lib.ImageService; public interface IImgService { - ImageResult GetImageEmbedData(string path); + ImageResult GetImageEmbedData(string filePath); + ImageResult EncodeSvg(string svgData); } diff --git a/Source/Bookgen.Lib/ImageService/ImageConverter.cs b/Source/Bookgen.Lib/ImageService/ImageConverter.cs index 6a8f6d41..e63500e4 100644 --- a/Source/Bookgen.Lib/ImageService/ImageConverter.cs +++ b/Source/Bookgen.Lib/ImageService/ImageConverter.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -18,12 +18,12 @@ public static void Encode(string source, string output, ImageType imageType, int if (Path.GetExtension("soruce").Equals(".svg", StringComparison.OrdinalIgnoreCase)) { - SKData img = Utils.RenderSvg(srcStream, width, height, GetRecodeOption(imageType)); + SKData img = ImageUtils.RenderSvg(srcStream, width, height, GetRecodeOption(imageType)); img.SaveTo(destStream); } using SKBitmap loaded = SKBitmap.Decode(srcStream); - using SKBitmap result = Utils.ResizeIfBigger(loaded, width, height); + using SKBitmap result = ImageUtils.ResizeIfBigger(loaded, width, height); result.Encode(destStream, imageType switch { diff --git a/Source/Bookgen.Lib/ImageService/Utils.cs b/Source/Bookgen.Lib/ImageService/ImageUtils.cs similarity index 86% rename from Source/Bookgen.Lib/ImageService/Utils.cs rename to Source/Bookgen.Lib/ImageService/ImageUtils.cs index b4600762..b8f0de05 100644 --- a/Source/Bookgen.Lib/ImageService/Utils.cs +++ b/Source/Bookgen.Lib/ImageService/ImageUtils.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -15,7 +15,7 @@ namespace Bookgen.Lib.ImageService; -internal static class Utils +internal static class ImageUtils { public static byte[] ConvertToPng(string file, int maxwidth, int maxHeight) { @@ -75,11 +75,23 @@ public static SKBitmap ResizeIfBigger(SKBitmap input, int width, int height) return input.Resize(new SKImageInfo(renderWidth, renderHeight), SKSamplingOptions.Default); } - public static SKData RenderSvg(Stream stream, int maxWidth, int maxHeight, SvgRecodeOption svgRecode) + private static SKSvg LoadSvg(Stream stream) { - using var svg = new SKSvg(); + var svg = new SKSvg(); svg.Load(stream); + return svg; + } + + private static SKSvg LoadSvg(string svgData) + { + var svg = new SKSvg(); + using var xmlReader = System.Xml.XmlReader.Create(new StringReader(svgData)); + svg.Load(xmlReader); + return svg; + } + private static SKData RenderSvg(SKSvg svg, int maxWidth, int maxHeight, SvgRecodeOption svgRecode) + { if (svg.Picture == null) return SKData.Empty; @@ -108,7 +120,18 @@ public static SKData RenderSvg(Stream stream, int maxWidth, int maxHeight, SvgRe }; } } + } + public static SKData RenderSvg(Stream svgsource, int maxWidth, int maxHeight, SvgRecodeOption svgRecode) + { + using var svg = LoadSvg(svgsource); + return RenderSvg(svg, maxWidth, maxHeight, svgRecode); + } + + public static SKData RenderSvg(string svgData, int maxWidth, int maxHeight, SvgRecodeOption svgRecode) + { + using var svg = LoadSvg(svgData); + return RenderSvg(svg, maxWidth, maxHeight, svgRecode); } public static SKData Encode(Stream fileData, int resizeWith, int resizeHeight, int quality, ImgRecodeOption imgRecodeOption) @@ -125,7 +148,6 @@ public static SKData Encode(Stream fileData, int resizeWith, int resizeHeight, i }, quality); } - public static string Base64Encode(Stream fileData) { const int readBufferSize = 4096; diff --git a/Source/Bookgen.Lib/ImageService/ImgService.cs b/Source/Bookgen.Lib/ImageService/ImgService.cs index e16ef2e4..efaa663b 100644 --- a/Source/Bookgen.Lib/ImageService/ImgService.cs +++ b/Source/Bookgen.Lib/ImageService/ImgService.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -50,50 +50,78 @@ private static ImageType GetImageType(string file) }; } - public ImageResult GetImageEmbedData(string path) + private static ImageType GetImateType(SvgRecodeOption recodeOption) { - static ImageType GetImateType(SvgRecodeOption recodeOption) + return recodeOption switch { - return recodeOption switch + SvgRecodeOption.AsPng => ImageType.Png, + SvgRecodeOption.AsWebp => ImageType.Webp, + SvgRecodeOption.Passtrough => ImageType.Svg, + _ => throw new UnreachableException(), + }; + } + + public ImageResult EncodeSvg(string svgData) + { + if (_imageConfig.SvgRecode == SvgRecodeOption.Passtrough) + { + return new ImageResult { - SvgRecodeOption.AsPng => ImageType.Png, - SvgRecodeOption.AsWebp => ImageType.Webp, - SvgRecodeOption.Passtrough => ImageType.Svg, - _ => throw new UnreachableException(), + Data = svgData, + ImageType = ImageType.Svg, + OriginalName = string.Empty, }; } - path = path.Replace("../", ""); - if (!IsImage(path)) - throw new InvalidOperationException($"{path} is not an image"); - if (!_sourceFolder.FileExists(path)) + using SKData rendered = ImageUtils.RenderSvg(svgData, + _imageConfig.ResizeWith, + _imageConfig.ResizeHeight, + _imageConfig.SvgRecode); + + return new ImageResult + { + Data = Convert.ToBase64String(rendered.AsSpan()), + ImageType = GetImateType(_imageConfig.SvgRecode), + OriginalName = string.Empty + }; + + } + + public ImageResult GetImageEmbedData(string filePath) + { + filePath = filePath.Replace("../", ""); + + if (!IsImage(filePath)) + throw new InvalidOperationException($"{filePath} is not an image"); + + if (!_sourceFolder.FileExists(filePath)) { - _logger.LogWarning("Image {Path} does not exist in source folder", path); + _logger.LogWarning("Image {Path} does not exist in source folder", filePath); return new ImageResult { Data = string.Empty, ImageType = ImageType.Png, - OriginalName = path, + OriginalName = filePath, }; } - using Stream fileData = _sourceFolder.OpenReadStream(path); + using Stream fileData = _sourceFolder.OpenReadStream(filePath); - if (string.Equals(Path.GetExtension(path), ".svg", StringComparison.CurrentCultureIgnoreCase)) + if (string.Equals(Path.GetExtension(filePath), ".svg", StringComparison.CurrentCultureIgnoreCase)) { if (_imageConfig.SvgRecode == SvgRecodeOption.Passtrough) { return new ImageResult { - Data = _sourceFolder.ReadAllText(path), + Data = _sourceFolder.ReadAllText(filePath), ImageType = ImageType.Svg, - OriginalName = path, + OriginalName = filePath, }; } - using SKData rendered = Utils.RenderSvg(fileData, + using SKData rendered = ImageUtils.RenderSvg(fileData, _imageConfig.ResizeWith, _imageConfig.ResizeHeight, _imageConfig.SvgRecode); @@ -102,14 +130,14 @@ static ImageType GetImateType(SvgRecodeOption recodeOption) { Data = Convert.ToBase64String(rendered.AsSpan()), ImageType = GetImateType(_imageConfig.SvgRecode), - OriginalName = path, + OriginalName = filePath, }; } if (_imageConfig.ResizeAndRecodeImages != ImgRecodeOption.Passtrough) { - using SKData rendered = Utils.Encode(fileData, + using SKData rendered = ImageUtils.Encode(fileData, _imageConfig.ResizeWith, _imageConfig.ResizeHeight, _imageConfig.ImageQualityOnResize, @@ -126,16 +154,16 @@ static ImageType GetImateType(SvgRecodeOption recodeOption) { Data = Convert.ToBase64String(rendered.AsSpan()), ImageType = type, - OriginalName = path, + OriginalName = filePath, }; } return new ImageResult { - Data = Utils.Base64Encode(fileData), - ImageType = GetImageType(path), - OriginalName = path, + Data = ImageUtils.Base64Encode(fileData), + ImageType = GetImageType(filePath), + OriginalName = filePath, }; } } diff --git a/Source/Bookgen.Lib/JsInterop/PrismJsInterop.cs b/Source/Bookgen.Lib/JsInterop/SyntaxRenderJsInterop.cs similarity index 53% rename from Source/Bookgen.Lib/JsInterop/PrismJsInterop.cs rename to Source/Bookgen.Lib/JsInterop/SyntaxRenderJsInterop.cs index cf32285f..bbb985f5 100644 --- a/Source/Bookgen.Lib/JsInterop/PrismJsInterop.cs +++ b/Source/Bookgen.Lib/JsInterop/SyntaxRenderJsInterop.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -7,16 +7,24 @@ namespace Bookgen.Lib.JsInterop; -public sealed class PrismJsInterop : JavascriptInterop +public sealed class SyntaxRenderJsInterop : JavascriptInterop { - public PrismJsInterop(IAssetSource assetSource) + private readonly IAssetSource _assetSource; + private bool _prismLoaded; + + public SyntaxRenderJsInterop(IAssetSource assetSource) { - string prismjs = assetSource.GetAsset(BundledAssets.PrismJs); - Execute(prismjs); + _assetSource = assetSource; } public string PrismSyntaxHighlight(string code, string language) { + if (!_prismLoaded) + { + string prismjs = _assetSource.GetAsset(BundledAssets.PrismJs); + Execute(prismjs); + _prismLoaded = true; + } _engine.Script.code = code; return ExecuteAndGetResult($"Prism.highlight(code, Prism.languages.{language}, '{language}');"); } diff --git a/Source/Bookgen.Lib/Markdown/RenderSettings.cs b/Source/Bookgen.Lib/Markdown/RenderSettings.cs index 21dba241..b6c9f0b6 100644 --- a/Source/Bookgen.Lib/Markdown/RenderSettings.cs +++ b/Source/Bookgen.Lib/Markdown/RenderSettings.cs @@ -14,7 +14,7 @@ public sealed class RenderSettings : IDisposable private readonly IImgService _imgService; public required string? HostUrl { get; init; } - public required PrismJsInterop? PrismJsInterop { get; init; } + public required SyntaxRenderJsInterop? PrismJsInterop { get; init; } public required CssClasses CssClasses { get; init; } public required bool DeleteFirstH1 { get; init; } public int OffsetHeadingsBy { get; init; } = 0; diff --git a/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs index 587f458e..e72f8548 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -19,7 +19,7 @@ namespace Bookgen.Lib.Markdown.Renderers; internal sealed class SyntaxRenderer : HtmlObjectRenderer, IDisposable { private readonly CodeBlockRenderer _originalRenderer; - private readonly PrismJsInterop? _prism; + private readonly SyntaxRenderJsInterop? _prism; private readonly HashSet _supportedLanguages; public const string Terminallanguage = "terminal"; @@ -32,7 +32,7 @@ internal sealed class SyntaxRenderer : HtmlObjectRenderer, IDisposabl public bool PreRender => _prism != null; - public SyntaxRenderer(CodeBlockRenderer underlyingRenderer, PrismJsInterop? prism) + public SyntaxRenderer(CodeBlockRenderer underlyingRenderer, SyntaxRenderJsInterop? prism) { _originalRenderer = underlyingRenderer ?? new CodeBlockRenderer(); _prism = prism; diff --git a/Source/Bookgen.Lib/Pipeline/Epub/CreateEpubCoverAndStyle.cs b/Source/Bookgen.Lib/Pipeline/Epub/CreateEpubCoverAndStyle.cs index d7d0a780..a40914a3 100644 --- a/Source/Bookgen.Lib/Pipeline/Epub/CreateEpubCoverAndStyle.cs +++ b/Source/Bookgen.Lib/Pipeline/Epub/CreateEpubCoverAndStyle.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -24,7 +24,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment var coverfile = await environment.Source.GetCoverFileName(environment.TableOfContents, logger); if (coverfile != null) { - byte[] coverdata = Utils.ConvertToPng(coverfile, 1200, 1200); + byte[] coverdata = ImageUtils.ConvertToPng(coverfile, 1200, 1200); await State.EpubFile.AddAsync("EPUB/cover.png", coverdata); State.PackageItems.Add(new PackageItem { diff --git a/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs b/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs index dd497091..ab21b7b7 100644 --- a/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -15,6 +15,7 @@ using Bookgen.Lib.Markdown; using Bookgen.Lib.Templates; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using static Bookgen.Lib.Pipeline.Epub.EpubState; @@ -23,8 +24,11 @@ namespace Bookgen.Lib.Pipeline.Epub; internal class CreateHtmlPages : PipeLineStep { - public CreateHtmlPages(EpubState state) : base(state) + private readonly IMemoryCache _memoryCache; + + public CreateHtmlPages(EpubState state, IMemoryCache memoryCache) : base(state) { + _memoryCache = memoryCache; } private string EpubImageRewrite(ImageResult result) @@ -44,14 +48,14 @@ public override async Task ExecuteAsync(IBookEnvironment environment ResizeWith = 1600, ResizeHeight = 1600, }); - var cached = new CachedImageService(imgService); + var cached = new CachedImageService(imgService, _memoryCache); using var settings = new RenderSettings(cached) { CssClasses = environment.Configuration.PrintConfig.CssClasses, DeleteFirstH1 = false, HostUrl = string.Empty, - PrismJsInterop = new PrismJsInterop(environment), + PrismJsInterop = new SyntaxRenderJsInterop(environment), OffsetHeadingsBy = 0, AutoEmbedSupportedLinks = false, ImageUrlRewriter = EpubImageRewrite diff --git a/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs b/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs index dfa0f799..efd1f88d 100644 --- a/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs +++ b/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -13,27 +13,31 @@ using Bookgen.Lib.Markdown; using Bookgen.Lib.Templates; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace Bookgen.Lib.Pipeline.Feed; internal sealed class CreateItems : PipeLineStep { - public CreateItems(SyndicationFeedState state) : base(state) + private readonly IMemoryCache _memoryCache; + + public CreateItems(SyndicationFeedState state, IMemoryCache memoryCache) : base(state) { + _memoryCache = memoryCache; } public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { var imgService = new ImgService(environment.Source, logger, environment.Configuration.FeedConfig.Images); - var cached = new CachedImageService(imgService); + var cached = new CachedImageService(imgService, _memoryCache); using var settings = new RenderSettings(cached) { CssClasses = environment.Configuration.FeedConfig.CssClasses, DeleteFirstH1 = false, HostUrl = string.Empty, - PrismJsInterop = environment.Configuration.FeedConfig.PreRenderCode ? new PrismJsInterop(environment) : null, + PrismJsInterop = environment.Configuration.FeedConfig.PreRenderCode ? new SyntaxRenderJsInterop(environment) : null, OffsetHeadingsBy = 0, AutoEmbedSupportedLinks = false }; diff --git a/Source/Bookgen.Lib/Pipeline/Pipeline.cs b/Source/Bookgen.Lib/Pipeline/Pipeline.cs index 53a96768..fcfad91b 100644 --- a/Source/Bookgen.Lib/Pipeline/Pipeline.cs +++ b/Source/Bookgen.Lib/Pipeline/Pipeline.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -10,6 +10,7 @@ using Bookgen.Lib.Pipeline.StaticWebsite; using Bookgen.Lib.Pipeline.Wordpress; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace Bookgen.Lib.Pipeline; @@ -42,28 +43,28 @@ public async Task ExecuteAsync(IBookEnvironment environment, ILogger logge return true; } - public static Pipeline CratePrintPipeLine() + public static Pipeline CratePrintPipeLine(IMemoryCache memoryCache) { var state = new PrintState(); return new Pipeline( - new RenderPages(state), + new RenderPages(state, memoryCache), new WriteHtml(state), new WriteXHtml(state) ); } - public static Pipeline CreatePostProcessPipeLine() + public static Pipeline CreatePostProcessPipeLine(IMemoryCache memoryCache) { var state = new PostProcessState(); return new Pipeline( - new RenderPagesForPostProcess(state), + new RenderPagesForPostProcess(state, memoryCache), new WriteFile(state) ); } - public static Pipeline CreateWebPipeLine() + public static Pipeline CreateWebPipeLine(IMemoryCache memoryCache) { var state = new StaticWebState(); @@ -72,26 +73,26 @@ public static Pipeline CreateWebPipeLine() new ExtractTemplateAssets(state), new ReadInFiles(state), new RenderTableOfContents(state), - new RenderStaticPages(state), - new RenderIndexPage(state), + new RenderStaticPages(state, memoryCache), + new RenderIndexPage(state, memoryCache), new RenderStabdaloneToc(state), new CreateEmptyIndexPagesForFolders(state), new GeneratePager(state) ); } - public static Pipeline CreateWordpressPipeLine() + public static Pipeline CreateWordpressPipeLine(IMemoryCache memoryCache) { var state = new WpState(); return new Pipeline( new CreateWpChannel(state), - new CreateWpPages(state), + new CreateWpPages(state, memoryCache), new WriteExportFile(state) ); } - public static Pipeline CreateEpubPileLine() + public static Pipeline CreateEpubPileLine(IMemoryCache memoryCache) { var state = new EpubState(); @@ -99,7 +100,7 @@ public static Pipeline CreateEpubPileLine() new Initialize(state), new CreateMimeType(state), new CreateContainer(state), - new CreateHtmlPages(state), + new CreateHtmlPages(state, memoryCache), new CreateImageFiles(state), new CreateFontFiles(state), new CreateEpubCoverAndStyle(state), @@ -110,12 +111,12 @@ public static Pipeline CreateEpubPileLine() } - public static Pipeline CreateFeedPipeline() + public static Pipeline CreateFeedPipeline(IMemoryCache memoryCache) { var state = new SyndicationFeedState(); return new Pipeline( new CreateFeed(state), - new CreateItems(state), + new CreateItems(state, memoryCache), new WriteFeeds(state) ); } diff --git a/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs b/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs index e8276fe9..2a040301 100644 --- a/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs +++ b/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -12,13 +12,19 @@ using Bookgen.Lib.JsInterop; using Bookgen.Lib.Markdown; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace Bookgen.Lib.Pipeline.PostProcess; internal sealed class RenderPagesForPostProcess : PipeLineStep { - public RenderPagesForPostProcess(PostProcessState state) : base(state) { } + private readonly IMemoryCache _memoryCache; + + public RenderPagesForPostProcess(PostProcessState state, IMemoryCache memoryCache) : base(state) + { + _memoryCache = memoryCache; + } public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { @@ -27,14 +33,14 @@ public override async Task ExecuteAsync(IBookEnvironment environment ResizeAndRecodeImages = ImgRecodeOption.Passtrough, SvgRecode = SvgRecodeOption.Passtrough, }); - var cached = new CachedImageService(imgService); + var cached = new CachedImageService(imgService, _memoryCache); using var settings = new RenderSettings(cached) { CssClasses = environment.Configuration.PrintConfig.CssClasses, DeleteFirstH1 = false, HostUrl = string.Empty, - PrismJsInterop = new PrismJsInterop(environment), + PrismJsInterop = new SyntaxRenderJsInterop(environment), OffsetHeadingsBy = 1, AutoEmbedSupportedLinks = false }; diff --git a/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs b/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs index 51fe6fa3..ce9460cc 100644 --- a/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -11,27 +11,31 @@ using Bookgen.Lib.Markdown; using Bookgen.Lib.Templates; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace Bookgen.Lib.Pipeline.Print; internal sealed class RenderPages : PipeLineStep { - public RenderPages(PrintState state) : base(state) + private readonly IMemoryCache _memoryCache; + + public RenderPages(PrintState state, IMemoryCache memoryCache) : base(state) { + _memoryCache = memoryCache; } public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { var imgService = new ImgService(environment.Source, logger, environment.Configuration.PrintConfig.Images); - var cached = new CachedImageService(imgService); + var cached = new CachedImageService(imgService, _memoryCache); using var settings = new RenderSettings(cached) { CssClasses = environment.Configuration.PrintConfig.CssClasses, DeleteFirstH1 = false, HostUrl = string.Empty, - PrismJsInterop = new PrismJsInterop(environment), + PrismJsInterop = new SyntaxRenderJsInterop(environment), OffsetHeadingsBy = 1, AutoEmbedSupportedLinks = false }; diff --git a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderIndexPage.cs b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderIndexPage.cs index 9ec2f662..161760fa 100644 --- a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderIndexPage.cs +++ b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderIndexPage.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -12,20 +12,24 @@ using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace Bookgen.Lib.Pipeline.StaticWebsite; internal sealed class RenderIndexPage : PipeLineStep { - public RenderIndexPage(StaticWebState state) : base(state) + private readonly IMemoryCache _memoryCache; + + public RenderIndexPage(StaticWebState state, IMemoryCache memoryCache) : base(state) { + _memoryCache = memoryCache; } public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { var imgService = new ImgService(environment.Source, logger, environment.Configuration.StaticWebsiteConfig.Images); - var cached = new CachedImageService(imgService); + var cached = new CachedImageService(imgService, _memoryCache); var renderer = new TemplateEngine(logger, environment); using var settings = new RenderSettings(cached) diff --git a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderStaticPages.cs b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderStaticPages.cs index 899de5df..3161e749 100644 --- a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderStaticPages.cs +++ b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderStaticPages.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -12,20 +12,24 @@ using BookGen.Vfs; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace Bookgen.Lib.Pipeline.StaticWebsite; internal sealed class RenderStaticPages : PipeLineStep { - public RenderStaticPages(StaticWebState state) : base(state) + private readonly IMemoryCache _memoryCache; + + public RenderStaticPages(StaticWebState state, IMemoryCache memoryCache) : base(state) { + _memoryCache = memoryCache; } public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { var imgService = new ImgService(environment.Source, logger, environment.Configuration.StaticWebsiteConfig.Images); - var cached = new CachedImageService(imgService); + var cached = new CachedImageService(imgService, _memoryCache); var renderer = new TemplateEngine(logger, environment); using var settings = new RenderSettings(cached) diff --git a/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs b/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs index f84c257b..36e4fad2 100644 --- a/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -15,6 +15,7 @@ using Bookgen.Lib.Markdown; using Bookgen.Lib.Templates; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace Bookgen.Lib.Pipeline.Wordpress; @@ -23,12 +24,14 @@ internal sealed class CreateWpPages : PipeLineStep { #if DEBUG private readonly HashSet _usedids; + private readonly IMemoryCache _memoryCache; #endif - public CreateWpPages(WpState state) : base(state) + public CreateWpPages(WpState state, IMemoryCache memoryCache) : base(state) { #if DEBUG _usedids = []; + _memoryCache = memoryCache; #endif } @@ -117,7 +120,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment logger.LogInformation("Creating pages..."); var imgService = new ImgService(environment.Source, logger, environment.Configuration.StaticWebsiteConfig.Images); - var cached = new CachedImageService(imgService); + var cached = new CachedImageService(imgService, _memoryCache); var renderer = new TemplateEngine(logger, environment); using var settings = new RenderSettings(cached) diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index 9f69682a..e5d49517 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -33,6 +33,12 @@ "resolved": "7.5.0", "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Direct", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==" + }, "SkiaSharp": { "type": "Direct", "requested": "[3.119.2, )", diff --git a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs index 360e6987..f2a377f8 100644 --- a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs +++ b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -179,7 +179,7 @@ public void EnsureThat_SourceCode_PreRender_Works() CssClasses = new CssClasses(), DeleteFirstH1 = false, HostUrl = "https://my.domain", - PrismJsInterop = new Bookgen.Lib.JsInterop.PrismJsInterop(testEnvironment), + PrismJsInterop = new Bookgen.Lib.JsInterop.SyntaxRenderJsInterop(testEnvironment), AutoEmbedSupportedLinks = true, }; From db768d1c47af518b21ae3270a454eda60da581f8 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sun, 1 Mar 2026 13:30:08 +0100 Subject: [PATCH 34/41] Nomnoml assets --- Assets/BundledAssets/graphre.js | 1 + Assets/BundledAssets/nomnoml.js | 1772 +++++++++++++++++++++++++++++++ 2 files changed, 1773 insertions(+) create mode 100644 Assets/BundledAssets/graphre.js create mode 100644 Assets/BundledAssets/nomnoml.js diff --git a/Assets/BundledAssets/graphre.js b/Assets/BundledAssets/graphre.js new file mode 100644 index 00000000..89b1500d --- /dev/null +++ b/Assets/BundledAssets/graphre.js @@ -0,0 +1 @@ +!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).graphre={})}(this,(function(e){"use strict";class r{constructor(){var e={};e._next=e._prev=e,this._sentinel=e}dequeue(){var e=this._sentinel,r=e._prev;if(r!==e)return n(r),r}enqueue(e){var r=this._sentinel,t=e;t._prev&&t._next&&n(t),t._next=r._next,r._next._prev=t,r._next=t,t._prev=r}toString(){for(var e=[],r=this._sentinel,n=r._prev;n!==r;)e.push(JSON.stringify(n,t)),n=n._prev;return"["+e.join(", ")+"]"}}function n(e){e._prev._next=e._next,e._next._prev=e._prev,delete e._next,delete e._prev}function t(e,r){if("_next"!==e&&"_prev"!==e)return r}var o=Object.freeze({__proto__:null,List:r});const i={};function a(e){var r=[];for(var n of e)r.push(...n);return r}function s(e,r){return null!=e&&e.hasOwnProperty(r)}function d(e){const r=null==e?0:e.length;return r?e[r-1]:void 0}function u(e,r){e=Object(e);const n={};return Object.keys(e).forEach((t=>{n[t]=r(e[t],t)})),n}function f(e,r){var n=Number.POSITIVE_INFINITY,t=void 0;for(var o of e){var i=r(o);ir(e)-r(n)))}function v(e){i[e]||(i[e]=0);return`${e}${++i[e]}`}function l(e){return e?Object.keys(e).map((r=>e[r])):[]}function g(e,r){for(var n=[],t=0;t!e.children(r).length)),t=g(Math.max(...n.map((r=>e.node(r).rank)))+1,(()=>[]));return c(n,(r=>e.node(r).rank)).forEach((function n(o){if(!s(r,o)){r[o]=!0;var i=e.node(o);t[i.rank].push(o),e.successors(o).forEach(n)}})),t}function b(e,r){for(var n=0,t=1;te.pos))}))),s=1;s0)),f=0;return i.forEach((function(e){var r=e.pos+s;u[r]+=e.weight;for(var n=0;r>0;)r%2&&(n+=u[r+1]),u[r=r-1>>1]+=e.weight;f+=e.weight*n})),f}function k(e,r){return r?r.map((function(r){var n=e.inEdges(r);if(n.length){var t=n.reduce((function(r,n){var t=e.edge(n),o=e.node(n.v);return{sum:r.sum+t.weight*o.order,weight:r.weight+t.weight}}),{sum:0,weight:0});return{v:r,barycenter:t.sum/t.weight,weight:t.weight}}return{v:r}})):[]}function E(e,r){for(var n={},t=0;t=e.barycenter)&&function(e,r){var n=0,t=0;e.weight&&(n+=e.barycenter*e.weight,t+=e.weight);r.weight&&(n+=r.barycenter*r.weight,t+=r.weight);e.vs=r.vs.concat(e.vs),e.barycenter=n/t,e.weight=t,e.i=Math.min(r.i,e.i),r.merged=!0}(e,r)}}function t(r){return function(n){n.in.push(r),0==--n.indegree&&e.push(n)}}for(;e.length;){var o=e.pop();r.push(o),o.in.reverse().forEach(n(o)),o.out.forEach(t(o))}return r.filter((e=>!e.merged)).map((function(e){var r={vs:e.vs,i:e.i};return"barycenter"in e&&(r.barycenter=e.barycenter),"weight"in e&&(r.weight=e.weight),r}))}(l(n).filter((e=>!e.indegree)))}var N="\0";class x{constructor(e={}){this._label=void 0,this._nodeCount=0,this._edgeCount=0,this._isDirected=!s(e,"directed")||e.directed,this._isMultigraph=!!s(e,"multigraph")&&e.multigraph,this._isCompound=!!s(e,"compound")&&e.compound,this._defaultNodeLabelFn=()=>{},this._defaultEdgeLabelFn=()=>{},this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children["\0"]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}isDirected(){return this._isDirected}isMultigraph(){return this._isMultigraph}isCompound(){return this._isCompound}setGraph(e){return this._label=e,this}graph(){return this._label}setDefaultNodeLabel(e){var r;return r=e,this._defaultNodeLabelFn="function"!=typeof r?()=>e:e,this}nodeCount(){return this._nodeCount}nodes(){return Object.keys(this._nodes)}sources(){var e=this;return this.nodes().filter((function(r){return w(e._in[r])}))}sinks(){var e=this;return this.nodes().filter((r=>w(e._out[r])))}setNodes(e,r){for(var n of e)void 0!==r?this.setNode(n,r):this.setNode(n);return this}setNode(e,r){return s(this._nodes,e)?(arguments.length>1&&(this._nodes[e]=r),this):(this._nodes[e]=arguments.length>1?r:this._defaultNodeLabelFn(e),this._isCompound&&(this._parent[e]=N,this._children[e]={},this._children["\0"][e]=!0),this._in[e]={},this._preds[e]={},this._out[e]={},this._sucs[e]={},++this._nodeCount,this)}node(e){return this._nodes[e]}hasNode(e){return s(this._nodes,e)}removeNode(e){var r=this;if(s(this._nodes,e)){var n=e=>{r.removeEdge(this._edgeObjs[e])};if(delete this._nodes[e],this._isCompound){for(var t of(this._removeFromParentsChildList(e),delete this._parent[e],this.children(e)))r.setParent(t);delete this._children[e]}for(var o of Object.keys(this._in[e]))n(o);for(var o of(delete this._in[e],delete this._preds[e],Object.keys(this._out[e])))n(o);delete this._out[e],delete this._sucs[e],--this._nodeCount}return this}setParent(e,r){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(void 0===r)r=N;else{for(var n=r+="";!p(n);n=this.parent(n))if(n===e)throw new Error(`Setting ${r} as parent of ${e} would create a cycle`);this.setNode(r)}return this.setNode(e),this._removeFromParentsChildList(e),this._parent[e]=r,this._children[r][e]=!0,this}_removeFromParentsChildList(e){delete this._children[this._parent[e]][e]}parent(e){if(this._isCompound){var r=this._parent[e];if(r!==N)return r}}children(e){if(p(e)&&(e=N),this._isCompound){var r=this._children[e];return r?Object.keys(r):void 0}return e===N?this.nodes():this.hasNode(e)?[]:void 0}predecessors(e){var r=this._preds[e];if(r)return Object.keys(r)}successors(e){var r=this._sucs[e];if(r)return Object.keys(r)}neighbors(e){var r=this.predecessors(e);if(r)return function(e,r){var n=[...e];for(var t of r)-1===n.indexOf(t)&&n.push(t);return n}(r,this.successors(e))}isLeaf(e){return 0===(this.isDirected()?this.successors(e):this.neighbors(e)).length}filterNodes(e){var r=new x({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});r.setGraph(this.graph());var n=this;m(this._nodes,(function(n,t){e(t)&&r.setNode(t,n)})),m(this._edgeObjs,(function(e){r.hasNode(e.v)&&r.hasNode(e.w)&&r.setEdge(e,n.edge(e))}));var t={};function o(e){var i=n.parent(e);return void 0===i||r.hasNode(i)?(t[e]=i,i):i in t?t[i]:o(i)}if(this._isCompound)for(var i of r.nodes())r.setParent(i,o(i));return r}setDefaultEdgeLabel(e){var r;return r=e,this._defaultEdgeLabelFn="function"!=typeof r?()=>e:e,this}edgeCount(){return this._edgeCount}edges(){return Object.values(this._edgeObjs)}setPath(e,r){var n=this,t=arguments;return e.reduce((function(e,o){return t.length>1?n.setEdge(e,o,r):n.setEdge(e,o),o})),this}setEdge(e,r,n,t){var o=!1,i=e;"object"==typeof i&&null!==i&&"v"in i?(e=i.v,r=i.w,t=i.name,2===arguments.length&&(n=arguments[1],o=!0)):(e=i,r=arguments[1],t=arguments[3],arguments.length>2&&(n=arguments[2],o=!0)),e=""+e,r=""+r,p(t)||(t=""+t);var a=j(this._isDirected,e,r,t);if(s(this._edgeLabels,a))return o&&(this._edgeLabels[a]=n),this;if(!p(t)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(e),this.setNode(r),this._edgeLabels[a]=o?n:this._defaultEdgeLabelFn(e,r,t);var d=function(e,r,n,t){var o=""+r,i=""+n;if(!e&&o>i){var a=o;o=i,i=a}var s={v:o,w:i};t&&(s.name=t);return s}(this._isDirected,e,r,t);return e=d.v,r=d.w,Object.freeze(d),this._edgeObjs[a]=d,C(this._preds[r],e),C(this._sucs[e],r),this._in[r][a]=d,this._out[e][a]=d,this._edgeCount++,this}edge(e,r,n){var t="object"==typeof e?M(this._isDirected,e):j(this._isDirected,e,r,n);return this._edgeLabels[t]}hasEdge(e,r,n){var t=1===arguments.length?M(this._isDirected,arguments[0]):j(this._isDirected,e,r,n);return s(this._edgeLabels,t)}removeEdge(e,r,n){var t="object"==typeof e?M(this._isDirected,e):j(this._isDirected,e,r,n),o=this._edgeObjs[t];return o&&(e=o.v,r=o.w,delete this._edgeLabels[t],delete this._edgeObjs[t],O(this._preds[r],e),O(this._sucs[e],r),delete this._in[r][t],delete this._out[e][t],this._edgeCount--),this}inEdges(e,r){var n=this._in[e];if(n){var t=Object.values(n);return r?t.filter((function(e){return e.v===r})):t}}outEdges(e,r){var n=this._out[e];if(n){var t=Object.values(n);return r?t.filter((function(e){return e.w===r})):t}}nodeEdges(e,r){var n=this.inEdges(e,r);if(n)return n.concat(this.outEdges(e,r))}}class I extends x{}function C(e,r){e[r]?e[r]++:e[r]=1}function O(e,r){--e[r]||delete e[r]}function j(e,r,n,t){var o=""+r,i=""+n;if(!e&&o>i){var a=o;o=i,i=a}return o+""+i+""+(p(t)?"\0":t)}function M(e,r){return j(e,r.v,r.w,r.name)}function L(e,r,n,t){var o;do{o=v(t)}while(e.hasNode(o));return n.dummy=r,e.setNode(o,n),o}function T(e){var r=(new x).setGraph(e.graph());for(var n of e.nodes())r.setNode(n,e.node(n));for(var t of e.edges()){var o=r.edge(t.v,t.w)||{weight:0,minlen:1},i=e.edge(t);r.setEdge(t.v,t.w,{weight:o.weight+i.weight,minlen:Math.max(o.minlen,i.minlen)})}return r}function S(e){var r=new x({multigraph:e.isMultigraph()}).setGraph(e.graph());for(var n of e.nodes())e.children(n).length||r.setNode(n,e.node(n));for(var t of e.edges())r.setEdge(t,e.edge(t));return r}function P(e,r){var n,t,o=e.x,i=e.y,a=r.x-o,s=r.y-i,d=e.width/2,u=e.height/2;if(!a&&!s)throw new Error("Not possible to find intersection inside of the rectangle");return Math.abs(s)*d>Math.abs(a)*u?(s<0&&(u=-u),n=u*a/s,t=u):(a<0&&(d=-d),n=d,t=d*s/a),{x:o+n,y:i+t}}function R(e){var r=g(G(e)+1,(()=>[]));for(var n of e.nodes()){var t=e.node(n),o=t.rank;void 0!==o&&(r[o][t.order]=n)}return r}function F(e){var r=Math.min(...e.nodes().map((r=>e.node(r).rank)).filter((e=>void 0!==e)));for(var n of e.nodes()){var t=e.node(n);s(t,"rank")&&(t.rank-=r)}}function D(e){var r=Math.min(...e.nodes().map((r=>e.node(r).rank)).filter((e=>void 0!==e))),n=[];for(var t of e.nodes()){var o=e.node(t).rank-r;n[o]||(n[o]=[]),n[o].push(t)}for(var i=0,a=e.graph().nodeRankFactor,s=0;s=4&&(o.rank=n,o.order=t),L(e,"border",o,r)}function G(e){var r=e.nodes().map((r=>e.node(r).rank)).filter((e=>void 0!==e));return Math.max(...r)}function V(e,r){var n=[],t=[];for(var o of e)r(o)?n.push(o):t.push(o);return{lhs:n,rhs:t}}function Y(e,r){var n=Date.now();try{return r()}finally{console.log(e+" time: "+(Date.now()-n)+"ms")}}function B(e,r){return r()}var A=Object.freeze({__proto__:null,addDummyNode:L,simplify:T,asNonCompoundGraph:S,successorWeights:function(e){var r={};for(var n of e.nodes()){var t={};for(var o of e.outEdges(n))t[o.w]=(t[o.w]||0)+e.edge(o).weight;r[n]=t}return r},predecessorWeights:function(e){var r={};for(var n of e.nodes()){var t={};for(var o of e.inEdges(n))t[o.v]=(t[o.v]||0)+e.edge(o).weight;r[n]=t}return r},intersectRect:P,buildLayerMatrix:R,normalizeRanks:F,removeEmptyRanks:D,addBorderNode:z,maxRank:G,partition:V,time:Y,notime:B});function q(e,r){var n,t=V(e,(function(e){return s(e,"barycenter")})),o=t.lhs,i=c(t.rhs,(e=>-e.i)),d=[],u=0,f=0,h=0;for(var v of(o.sort((n=!!r,function(e,r){return e.barycenterr.barycenter?1:n?r.i-e.i:e.i-r.i})),h=W(d,i,h),o))h+=v.vs.length,d.push(v.vs),u+=v.barycenter*v.weight,f+=v.weight,h=W(d,i,h);var l={vs:a(d)};return f&&(l.barycenter=u/f,l.weight=f),l}function W(e,r,n){for(var t;r.length&&(t=d(r)).i<=n;)r.pop(),e.push(t.vs),n++;return n}function $(e,r,n,t){var o=e.children(r),i=e.node(r),d=i?i.borderLeft:void 0,u=i?i.borderRight:void 0,f={};d&&(o=o.filter((e=>e!==d&&e!==u)));var h=k(e,o);for(var c of h)if(e.children(c.v).length){var v=$(e,c.v,n,t);f[c.v]=v,s(v,"barycenter")&&J(c,v)}var l=E(h,n);!function(e,r){for(var n of e)n.vs=a(n.vs.map((function(e){return r[e]?r[e].vs:[e]})))}(l,f);var g=q(l,t);if(d&&(g.vs=[d,...g.vs,u],e.predecessors(d).length)){var p=e.node(e.predecessors(d)[0]),m=e.node(e.predecessors(u)[0]);s(g,"barycenter")||(g.barycenter=0,g.weight=0),g.barycenter=(g.barycenter*g.weight+p.order+m.order)/(g.weight+2),g.weight+=2}return g}function J(e,r){void 0!==e.barycenter?(e.barycenter=(e.barycenter*e.weight+r.barycenter*r.weight)/(e.weight+r.weight),e.weight+=r.weight):(e.barycenter=r.barycenter,e.weight=r.weight)}function Q(e,r,n){var t=function(e){var r;for(;e.hasNode(r=v("_root")););return r}(e),o=new x({compound:!0}).setGraph({root:t}).setDefaultNodeLabel((r=>e.node(r)));for(var i of e.nodes()){var a=e.node(i),d=e.parent(i);if(a.rank===r||a.minRank<=r&&r<=a.maxRank){for(var u of(o.setNode(i),o.setParent(i,d||t),e[n](i))){var f=u.v===i?u.w:u.v,h=o.edge(f,i),c=void 0!==h?h.weight:0;o.setEdge(f,i,{weight:e.edge(u).weight+c})}s(a,"minRank")&&o.setNode(i,{borderLeft:a.borderLeft[r],borderRight:a.borderRight[r]})}}return o}function K(e,r,n){var t,o={};for(var i of n)!function(){for(var n,a=e.parent(i);a;){var s=e.parent(a);if(s?(n=o[s],o[s]=a):(n=t,t=a),n&&n!==a)return void r.setEdge(n,a);a=s}}()}function X(e){var r=G(e),n=H(e,h(1,r+1),"inEdges"),t=H(e,h(r-1,-1),"outEdges"),o=_(e);Z(e,o);for(var i,a=Number.POSITIVE_INFINITY,s=0,d=0;d<4;++s,++d){U(s%2?n:t,s%4>=2);var u=b(e,o=R(e));ue.slice(0))),a=u)}Z(e,i)}function H(e,r,n){return r.map((r=>Q(e,r,n)))}function U(e,r){var n=new x;for(var t of e){var o=t.graph().root,i=$(t,o,n,r);i.vs.map((function(e,r){t.node(e).order=r})),K(t,n,i.vs)}}function Z(e,r){for(var n of r)n.map((function(r,n){e.node(r).order=n}))}var ee=Object.freeze({__proto__:null,order:X,addSubgraphConstraints:K,barycenter:k,buildLayerGraph:Q,crossCount:b,initOrder:_,resolveConflicts:E,sortSubgraph:$,sort:q});function re(e,r){var n={};return r.reduce((function(r,t){for(var o=0,i=0,a=r.length,s=d(t),u=0;ua)&&oe(n,u,s)}}return r.reduce((function(r,n){for(var o,i=-1,a=0,s=0;sn){var t=r;r=n,n=t}var o=e[r];o||(e[r]=o={}),o[n]=!0}function ie(e,r,n){if(r>n){var t=r;r=n,n=t}return s(e[r],n)}function ae(e,r,n,t){var o={},i={},a={};for(var s of r)for(var d=0;da[e]))).length-1)/2,l=Math.floor(v),g=Math.ceil(v);l<=g;++l){var p=h[l];i[f]===f&&ua.predecessors(e))),d((function(r){var n=a.outEdges(r).reduce((function(e,r){return Math.min(e,i[r.w]-a.edge(r))}),Number.POSITIVE_INFINITY),t=e.node(r);n!==Number.POSITIVE_INFINITY&&t.borderType!==s&&(i[r]=Math.max(i[r],n))}),(e=>a.successors(e))),Object.keys(t))){var f=t[u];i[f]=i[n[f]]}return i}function de(e,r){return f(l(r),(function(r){var n=Number.NEGATIVE_INFINITY,t=Number.POSITIVE_INFINITY;for(var o in r){var i=r[o],a=ve(e,o)/2;n=Math.max(i+a,n),t=Math.min(i-a,t)}return n-t}))}function ue(e,r){var n=l(r),t=Math.min(...n),o=Math.max(...n);for(var i of["ul","ur","dl","dr"]){var a=i[1],s=e[i];if(s!==r){var d=l(s),f="l"===a?t-Math.min(...d):o-Math.max(...d);f&&(e[i]=u(s,(e=>e+f)))}}}function fe(e,r){return u(e.ul,(function(n,t){if(r)return e[r.toLowerCase()][t];var o=c([e.ul[t],e.ur[t],e.dl[t],e.dr[t]],(e=>e));return(o[1]+o[2])/2}))}function he(e){var r,n=R(e),t=Object.assign(Object.assign({},re(e,n)),ne(e,n)),o={ul:{},ur:{},dl:{},dr:{}};for(var i of["u","d"])for(var a of(r="u"===i?n:n.map((e=>e)).reverse(),["l","r"])){"r"===a&&(r=r.map((e=>e.map((e=>e)).reverse())));var s=ae(0,r,t,("u"===i?e.predecessors:e.successors).bind(e)),d=se(e,r,s.root,s.align,"r"===a);"r"===a&&(d=u(d,(e=>-e))),o[i+a]=d}return ue(o,de(e,o)),fe(o,e.graph().align)}function ce(e,r,n){return function(t,o,i){var a,d=t.node(o),u=t.node(i),f=0;if(f+=d.width/2,s(d,"labelpos"))switch(d.labelpos.toLowerCase()){case"l":a=-d.width/2;break;case"r":a=d.width/2}if(a&&(f+=n?a:-a),a=0,f+=(d.dummy?r:e)/2,f+=(u.dummy?r:e)/2,f+=u.width/2,s(u,"labelpos"))switch(u.labelpos.toLowerCase()){case"l":a=u.width/2;break;case"r":a=-u.width/2}return a&&(f+=n?a:-a),a=0,f}}function ve(e,r){return e.node(r).width}var le=Object.freeze({__proto__:null,findType1Conflicts:re,findType2Conflicts:ne,findOtherInnerSegmentNode:te,addConflict:oe,hasConflict:ie,verticalAlignment:ae,horizontalCompaction:se,findSmallestWidthAlignment:de,alignCoordinates:ue,balance:fe,positionX:he,sep:ce,width:ve});function ge(e){!function(e){var r=R(e),n=e.graph().ranksep,t=0;for(var o of r){var i=Math.max(...o.map((r=>e.node(r).height)));for(var a of o)e.node(a).y=t+i/2;t+=i+n}}(e=S(e));var r=he(e);for(var n in r)e.node(n).x=r[n]}var pe=Object.freeze({__proto__:null,bk:le,position:ge});function me(e){var r={};e.sources().forEach((function n(t){var o=e.node(t);if(s(r,t))return o.rank;r[t]=!0;var i=Math.min(...e.outEdges(t).map((r=>n(r.w)-e.edge(r).minlen)));return i!==Number.POSITIVE_INFINITY&&null!=i||(i=0),o.rank=i}))}function we(e,r){return e.node(r.w).rank-e.node(r.v).rank-e.edge(r).minlen}function _e(e){var r,n=new x({directed:!1}),t=e.nodes()[0],o=e.nodeCount();for(n.setNode(t,{});i(e)this._arr[n].priority)throw new Error("New priority is greater than current priority. Key: "+e+" Old: "+this._arr[n].priority+" New: "+r);this._arr[n].priority=r,this._decrease(n)}_heapify(e){var r=this._arr,n=2*e,t=n+1,o=e;n>1].priority1;function ke(e,r,n,t){return function(e,r,n,t){var o,i,a={},s=new be,d=function(e){var r=e.v!==o?e.v:e.w,t=a[r],d=n(e),u=i.distance+d;if(d<0)throw new Error("dijkstra does not allow negative edge weights. Bad edge: "+e+" Weight: "+d);u0&&(o=s.removeMin(),(i=a[o]).distance!==Number.POSITIVE_INFINITY);)t(o).forEach(d);return a}(e,String(r),n||ye,t||function(r){return e.outEdges(r)})}function Ee(e){var r=0,n=[],t={},o=[];function i(a){var s=t[a]={onStack:!0,lowlink:r,index:r++};if(n.push(a),e.successors(a).forEach((function(e){e in t?t[e].onStack&&(s.lowlink=Math.min(s.lowlink,t[e].index)):(i(e),s.lowlink=Math.min(s.lowlink,t[e].lowlink))})),s.lowlink===s.index){var d,u=[];do{d=n.pop(),t[d].onStack=!1,u.push(d)}while(a!==d);o.push(u)}}return e.nodes().forEach((function(e){e in t||i(e)})),o}var Ne=()=>1;class xe extends Error{}function Ie(e){var r={},n={},t=[];function o(i){if(i in n)throw new xe;if(!(i in r)){for(var a of(n[i]=!0,r[i]=!0,e.predecessors(i)))o(a);delete n[i],t.push(i)}}for(var i of e.sinks())o(i);if(Object.keys(r).length!==e.nodeCount())throw new xe;return t}function Ce(e,r,n){var t=Array.isArray(r)?r:[r],o=(e.isDirected()?e.successors:e.neighbors).bind(e),i=[],a={};for(var s of t){if(!e.hasNode(s))throw new Error("Graph does not have node: "+s);Oe(e,s,"post"===n,a,o,i)}return i}function Oe(e,r,n,t,o,i){if(!(r in t)){for(var a of(t[r]=!0,n||i.push(r),o(r)))Oe(e,a,n,t,o,i);n&&i.push(r)}}function je(e,r){return Ce(e,r,"post")}function Me(e,r){return Ce(e,r,"pre")}var Le=Object.freeze({__proto__:null,components:function(e){var r,n={},t=[];function o(t){if(!(t in n)){for(var i of(n[t]=!0,r.push(t),e.successors(t)))o(i);for(var a of e.predecessors(t))o(a)}}for(var i of e.nodes())r=[],o(i),r.length&&t.push(r);return t},dijkstra:ke,dijkstraAll:function(e,r,n){var t={};for(var o of e.nodes())t[o]=ke(e,o,r,n);return t},findCycles:function(e){return Ee(e).filter((function(r){return r.length>1||1===r.length&&e.hasEdge(r[0],r[0])}))},floydWarshall:function(e,r,n){return function(e,r,n){var t={},o=e.nodes();return o.forEach((function(e){t[e]={},t[e][e]={distance:0},o.forEach((function(r){e!==r&&(t[e][r]={distance:Number.POSITIVE_INFINITY})})),n(e).forEach((function(n){var o=n.v===e?n.w:n.v,i=r(n);t[e][o]={distance:i,predecessor:e}}))})),o.forEach((function(e){var r=t[e];o.forEach((function(n){var i=t[n];o.forEach((function(n){var t=i[e],o=r[n],a=i[n],s=t.distance+o.distance;s0;){if((n=i.removeMin())in o)t.setEdge(n,o[n]);else{if(s)throw new Error("Input graph is not connected: "+e);s=!0}e.nodeEdges(n).forEach(a)}return t},tarjan:Ee,topsort:Ie});function Te(e){me(e=T(e));var r,n=_e(e);for(Fe(n),Se(n,e);r=ze(n);)Ve(n,e,r,Ge(n,e,r))}function Se(e,r){var n=je(e,e.nodes());for(var t of n=n.slice(0,n.length-1))Pe(e,r,t)}function Pe(e,r,n){var t=e.node(n).parent;e.edge(n,t).cutvalue=Re(e,r,n)}function Re(e,r,n){var t,o,i=e.node(n).parent,a=!0,s=r.edge(n,i),d=0;for(var u of(s||(a=!1,s=r.edge(i,n)),d=s.weight,r.nodeEdges(n))){var f=u.v===n,h=f?u.w:u.v;if(h!==i){var c=f===a,v=r.edge(u).weight;if(d+=c?v:-v,t=n,o=h,e.hasEdge(t,o)){var l=e.edge(n,h).cutvalue;d+=c?-l:l}}}return d}function Fe(e,r){arguments.length<2&&(r=e.nodes()[0]),De(e,{},1,r)}function De(e,r,n,t,o){var i=n,a=e.node(t);for(var d of(r[t]=!0,e.neighbors(t)))s(r,d)||(n=De(e,r,n,d,t));return a.low=i,a.lim=n++,o?a.parent=o:delete a.parent,n}function ze(e){for(var r of e.edges())if(e.edge(r).cutvalue<0)return r}function Ge(e,r,n){var t=n.v,o=n.w;r.hasEdge(t,o)||(t=n.w,o=n.v);var i=e.node(t),a=e.node(o),s=i,d=!1;return i.lim>a.lim&&(s=a,d=!0),f(r.edges().filter((function(r){return d===Ye(e,e.node(r.v),s)&&d!==Ye(e,e.node(r.w),s)})),(e=>we(r,e)))}function Ve(e,r,n,t){var o=n.v,i=n.w;e.removeEdge(o,i),e.setEdge(t.v,t.w,{}),Fe(e),Se(e,r),function(e,r){var n=function(e,r){for(var n of e.nodes())if(!r.node(n).parent)return n;return}(e,r),t=Me(e,n);for(var o of t=t.slice(1)){var i=e.node(o).parent,a=r.edge(o,i),s=!1;a||(a=r.edge(i,o),s=!0),r.node(o).rank=r.node(i).rank+(s?a.minlen:-a.minlen)}}(e,r)}function Ye(e,r,n){return n.low<=r.lim&&r.lim<=n.lim}function Be(e){switch(e.graph().ranker){case"network-simplex":We(e);break;case"tight-tree":qe(e);break;case"longest-path":Ae(e);break;default:We(e)}}Te.initLowLimValues=Fe,Te.initCutValues=Se,Te.calcCutValue=Re,Te.leaveEdge=ze,Te.enterEdge=Ge,Te.exchangeEdges=Ve;var Ae=me;function qe(e){me(e),_e(e)}function We(e){Te(e)}var $e=Object.freeze({__proto__:null,rank:Be,tightTreeRanker:qe,networkSimplexRanker:We,networkSimplex:Te,feasibleTree:_e,longestPath:me}),Je=e=>1;function Qe(e,n){if(e.nodeCount()<=1)return[];var t=function(e,n){var t=new x,o=0,i=0;for(var a of e.nodes())t.setNode(a,{v:a,in:0,out:0});for(var s of e.edges()){var d=t.edge(s.v,s.w)||0,u=n(s),f=d+u;t.setEdge(s.v,s.w,f),i=Math.max(i,t.node(s.v).out+=u),o=Math.max(o,t.node(s.w).in+=u)}var h=g(i+o+3,(()=>new r)),c=o+1;for(var a of t.nodes())Xe(h,c,t.node(a));return{graph:t,buckets:h,zeroIdx:c}}(e,n||Je);return a(function(e,r,n){var t,o=[],i=r[r.length-1],a=r[0];for(;e.nodeCount();){for(;t=a.dequeue();)Ke(e,r,n,t);for(;t=i.dequeue();)Ke(e,r,n,t);if(e.nodeCount())for(var s=r.length-2;s>0;--s)if(t=r[s].dequeue()){o=o.concat(Ke(e,r,n,t,!0));break}}return o}(t.graph,t.buckets,t.zeroIdx).map((r=>e.outEdges(r.v,r.w))))}function Ke(e,r,n,t,o){var i=o?[]:void 0;for(var a of e.inEdges(t.v)){var s=e.edge(a),d=e.node(a.v);o&&i.push({v:a.v,w:a.w}),d.out-=s,Xe(r,n,d)}for(var a of e.outEdges(t.v)){s=e.edge(a);var u=a.w,f=e.node(u);f.in-=s,Xe(r,n,f)}return e.removeNode(t.v),i}function Xe(e,r,n){n.out?n.in?e[n.out-n.in+r].enqueue(n):e[e.length-1].enqueue(n):e[0].enqueue(n)}var He={run:function(e){var r="greedy"===e.graph().acyclicer?Qe(e,function(e){return function(r){return e.edge(r).weight}}(e)):function(e){var r=[],n={},t={};function o(i){if(!s(t,i)){for(var a of(t[i]=!0,n[i]=!0,e.outEdges(i)))s(n,a.w)?r.push(a):o(a.w);delete n[i]}}return e.nodes().forEach(o),r}(e);for(var n of r){var t=e.edge(n);e.removeEdge(n),t.forwardName=n.name,t.reversed=!0,e.setEdge(n.w,n.v,t,v("rev"))}},undo:function(e){for(var r of e.edges()){var n=e.edge(r);if(n.reversed){e.removeEdge(r);var t=n.forwardName;delete n.reversed,delete n.forwardName,e.setEdge(r.w,r.v,n,t)}}}};function Ue(e){e.children().forEach((function r(n){var t=e.children(n),o=e.node(n);if(t.length&&t.forEach(r),s(o,"minRank")){o.borderLeft=[],o.borderRight=[];for(var i=o.minRank,a=o.maxRank+1;id||u>r[o].lim));for(i=o,o=t;(o=e.parent(o))!==i;)s.push(o);return{path:a.concat(s.reverse()),lca:i}}var fr={run:function(e){var r=L(e,"root",{},"_root"),n=function(e){var r={};function n(t,o){var i=e.children(t);if(i&&i.length)for(var a of i)n(a,o+1);r[t]=o}for(var t of e.children())n(t,1);return r}(e),t=Math.max(...l(n))-1,o=2*t+1;for(var i of(e.graph().nestingRoot=r,e.edges()))e.edge(i).minlen*=o;var a=function(e){return e.edges().reduce(((r,n)=>r+e.edge(n).weight),0)}(e)+1;for(var s of e.children())hr(e,r,o,a,t,n,s);e.graph().nodeRankFactor=o},cleanup:function(e){var r=e.graph();for(var n of(e.removeNode(r.nestingRoot),delete r.nestingRoot,e.edges())){e.edge(n).nestingEdge&&e.removeEdge(n)}}};function hr(e,r,n,t,o,i,a){var s=e.children(a);if(s.length){var d=z(e,"_bt"),u=z(e,"_bb"),f=e.node(a);for(var h of(e.setParent(d,a),f.borderTop=d,e.setParent(u,a),f.borderBottom=u,s)){hr(e,r,n,t,o,i,h);var c=e.node(h),v=c.borderTop?c.borderTop:h,l=c.borderBottom?c.borderBottom:h,g=c.borderTop?t:2*t,p=v!==l?1:o-i[a]+1;e.setEdge(d,v,{weight:g,minlen:p,nestingEdge:!0}),e.setEdge(l,u,{weight:g,minlen:p,nestingEdge:!0})}e.parent(a)||e.setEdge(r,d,{weight:0,minlen:o+i[a]})}else a!==r&&e.setEdge(r,a,{weight:0,minlen:n})}function cr(e){return"edge-proxy"==e.dummy}function vr(e){return"selfedge"==e.dummy}var lr=50,gr=20,pr=50,mr="tb",wr=1,_r=1,br=0,yr=0,kr=10,Er="r";function Nr(e={}){var r={};for(var n of Object.keys(e))r[n.toLowerCase()]=e[n];return r}function xr(e){return e.nodes().map((function(r){var n=e.node(r),t=e.parent(r),o={v:r};return void 0!==n&&(o.value=n),void 0!==t&&(o.parent=t),o}))}function Ir(e){return e.edges().map((function(r){var n=e.edge(r),t={v:r.v,w:r.w};return void 0!==r.name&&(t.name=r.name),void 0!==n&&(t.value=n),t}))}var Cr=Object.freeze({__proto__:null,write:function(e){var r={options:{directed:e.isDirected(),multigraph:e.isMultigraph(),compound:e.isCompound()},nodes:xr(e),edges:Ir(e)};return void 0!==e.graph()&&(r.value=JSON.parse(JSON.stringify(e.graph()))),r},read:function(e){var r=new x(e.options).setGraph(e.value);for(var n of e.nodes)r.setNode(n.v,n.value),n.parent&&r.setParent(n.v,n.parent);for(var n of e.edges)r.setEdge({v:n.v,w:n.w,name:n.name},n.value);return r}}),Or={Graph:x,GraphLike:I,alg:Le,json:Cr,PriorityQueue:be};e.Graph=x,e.GraphLike=I,e.PriorityQueue=be,e.acyclic=He,e.addBorderSegments=Ue,e.alg=Le,e.coordinateSystem=er,e.data=o,e.debug=ir,e.graphlib=Or,e.greedyFAS=Qe,e.json=Cr,e.layout=function(e,r){var n=r&&r.debugTiming?Y:B;n("layout",(function(){var r=n(" buildLayoutGraph",(function(){return function(e){var r,n,t,o,i,a,s,d,u,f,h,c,v,l,g,p=new x({multigraph:!0,compound:!0}),m=Nr(e.graph()),w={nodesep:null!==(r=m.nodesep)&&void 0!==r?r:pr,edgesep:null!==(n=m.edgesep)&&void 0!==n?n:gr,ranksep:null!==(t=m.ranksep)&&void 0!==t?t:lr,marginx:+(null!==(o=m.marginx)&&void 0!==o?o:0),marginy:+(null!==(i=m.marginy)&&void 0!==i?i:0),acyclicer:m.acyclicer,ranker:null!==(a=m.ranker)&&void 0!==a?a:"network-simplex",rankdir:null!==(s=m.rankdir)&&void 0!==s?s:mr,align:m.align};for(var _ of(p.setGraph(w),e.nodes())){var b=Nr(e.node(_)),y={width:+(null!==(d=b&&b.width)&&void 0!==d?d:0),height:+(null!==(u=b&&b.height)&&void 0!==u?u:0)};p.setNode(_,y),p.setParent(_,e.parent(_))}for(var k of e.edges()){var E=Nr(e.edge(k)),N={minlen:null!==(f=E.minlen)&&void 0!==f?f:wr,weight:null!==(h=E.weight)&&void 0!==h?h:_r,width:null!==(c=E.width)&&void 0!==c?c:br,height:null!==(v=E.height)&&void 0!==v?v:yr,labeloffset:null!==(l=E.labeloffset)&&void 0!==l?l:kr,labelpos:null!==(g=E.labelpos)&&void 0!==g?g:Er};p.setEdge(k,N)}return p}(e)}));n(" runLayout",(function(){!function(e,r){r(" makeSpaceForEdgeLabels",(function(){!function(e){var r=e.graph();for(var n of(r.ranksep/=2,e.edges())){var t=e.edge(n);t.minlen*=2,"c"!==t.labelpos.toLowerCase()&&("TB"===r.rankdir||"BT"===r.rankdir?t.width+=t.labeloffset:t.height+=t.labeloffset)}}(e)})),r(" removeSelfEdges",(function(){!function(e){for(var r of e.edges())if(r.v===r.w){var n=e.node(r.v);n.selfEdges||(n.selfEdges=[]),n.selfEdges.push({e:r,label:e.edge(r)}),e.removeEdge(r)}}(e)})),r(" acyclic",(function(){He.run(e)})),r(" nestingGraph.run",(function(){fr.run(e)})),r(" rank",(function(){Be(S(e))})),r(" injectEdgeLabelProxies",(function(){!function(e){for(var r of e.edges()){var n=e.edge(r);if(n.width&&n.height){var t=e.node(r.v),o=e.node(r.w);L(e,"edge-proxy",{rank:(o.rank-t.rank)/2+t.rank,e:r},"_ep")}}}(e)})),r(" removeEmptyRanks",(function(){D(e)})),r(" nestingGraph.cleanup",(function(){fr.cleanup(e)})),r(" normalizeRanks",(function(){F(e)})),r(" assignRankMinMax",(function(){!function(e){var r=0;for(var n of e.nodes()){var t=e.node(n);t.borderTop&&(t.minRank=e.node(t.borderTop).rank,t.maxRank=e.node(t.borderBottom).rank,r=Math.max(r,t.maxRank))}e.graph().maxRank=r}(e)})),r(" removeEdgeLabelProxies",(function(){!function(e){for(var r of e.nodes()){var n=e.node(r);cr(n)&&(e.edge(n.e).labelRank=n.rank,e.removeNode(r))}}(e)})),r(" normalize.run",(function(){ar.run(e)})),r(" parentDummyChains",(function(){dr(e)})),r(" addBorderSegments",(function(){Ue(e)})),r(" order",(function(){X(e)})),r(" insertSelfEdges",(function(){!function(e){var r,n=R(e);for(var t of n)for(var o=0,i=0;i e.width ?? 0)); + clas.height = sum(clas.parts, (e) => e.height ?? 0 ?? 0); + clas.dividers = []; + let y = 0; + for (const comp of clas.parts) { + comp.x = 0 + offset.x; + comp.y = y + offset.y; + comp.width = clas.width; + y += comp.height ?? 0 ?? 0; + if (comp != last(clas.parts)) + clas.dividers.push([ + { x: 0, y: y }, + { x: clas.width, y: y }, + ]); + } + } + function box(config, clas) { + offsetBox(config, clas, { x: 0, y: 0 }); + } + function icon(config, clas) { + clas.dividers = []; + clas.parts = []; + clas.width = config.fontSize * 2.5; + clas.height = config.fontSize * 2.5; + } + function labelledIcon(config, clas) { + clas.width = config.fontSize * 1.5; + clas.height = config.fontSize * 1.5; + clas.dividers = []; + let y = config.direction == 'LR' ? clas.height - config.padding : -clas.height / 2; + for (const comp of clas.parts) { + if (config.direction == 'LR') { + comp.x = clas.width / 2 - (comp.width ?? 0) / 2; + comp.y = y; + } + else { + comp.x = clas.width / 2 + config.padding / 2; + comp.y = y; + } + y += comp.height ?? 0 ?? 0; + } + } + const layouters = { + actor: function (config, clas) { + clas.width = Math.max(config.padding * 2, ...clas.parts.map((e) => e.width ?? 0)); + clas.height = config.padding * 3 + sum(clas.parts, (e) => e.height ?? 0); + clas.dividers = []; + let y = config.padding * 3; + for (const comp of clas.parts) { + comp.x = 0; + comp.y = y; + comp.width = clas.width; + y += comp.height ?? 0; + if (comp != last(clas.parts)) + clas.dividers.push([ + { x: config.padding, y: y }, + { x: clas.width - config.padding, y: y }, + ]); + } + }, + class: box, + database: function (config, clas) { + clas.width = Math.max(...clas.parts.map((e) => e.width ?? 0)); + clas.height = sum(clas.parts, (e) => e.height ?? 0) + config.padding * 2; + clas.dividers = []; + let y = config.padding * 1.5; + for (const comp of clas.parts) { + comp.x = 0; + comp.y = y; + comp.width = clas.width; + y += comp.height ?? 0; + if (comp != last(clas.parts)) { + const path = range([0, Math.PI], 16).map((a) => ({ + x: clas.width * 0.5 * (1 - Math.cos(a)), + y: y + config.padding * (0.75 * Math.sin(a) - 0.5), + })); + clas.dividers.push(path); + } + } + }, + ellipse: function (config, clas) { + const width = Math.max(...clas.parts.map((e) => e.width ?? 0)); + const height = sum(clas.parts, (e) => e.height ?? 0); + clas.width = width * 1.25; + clas.height = height * 1.25; + clas.dividers = []; + let y = height * 0.125; + const sq = (x) => x * x; + const rimPos = (y) => Math.sqrt(sq(0.5) - sq(y / clas.height - 0.5)) * clas.width; + for (const comp of clas.parts) { + comp.x = width * 0.125; + comp.y = y; + comp.width = width; + y += comp.height ?? 0; + if (comp != last(clas.parts)) + clas.dividers.push([ + { x: clas.width / 2 + rimPos(y) - 1, y: y }, + { x: clas.width / 2 - rimPos(y) + 1, y: y }, + ]); + } + }, + end: icon, + frame: function (config, clas) { + const w = clas.parts[0].width ?? 0; + const h = clas.parts[0].height ?? 0; + clas.parts[0].width = h / 2 + (clas.parts[0].width ?? 0); + box(config, clas); + if (clas.dividers?.length) + clas.dividers.shift(); + clas.dividers?.unshift([ + { x: 0, y: h }, + { x: w - h / 4, y: h }, + { x: w + h / 4, y: h / 2 }, + { x: w + h / 4, y: 0 }, + ]); + }, + hidden: function (config, clas) { + clas.dividers = []; + clas.parts = []; + clas.width = 1; + clas.height = 1; + }, + input: box, + lollipop: labelledIcon, + none: box, + note: box, + package: box, + pipe: function box(config, clas) { + offsetBox(config, clas, { x: -config.padding / 2, y: 0 }); + }, + receiver: box, + rhomb: function (config, clas) { + const width = Math.max(...clas.parts.map((e) => e.width ?? 0)); + const height = sum(clas.parts, (e) => e.height ?? 0); + clas.width = width * 1.5; + clas.height = height * 1.5; + clas.dividers = []; + let y = height * 0.25; + for (const comp of clas.parts) { + comp.x = width * 0.25; + comp.y = y; + comp.width = width; + y += comp.height ?? 0; + const slope = clas.width / clas.height; + if (comp != last(clas.parts)) + clas.dividers.push([ + { + x: clas.width / 2 + (y < clas.height / 2 ? y * slope : (clas.height - y) * slope), + y: y, + }, + { + x: clas.width / 2 - (y < clas.height / 2 ? y * slope : (clas.height - y) * slope), + y: y, + }, + ]); + } + }, + roundrect: box, + sender: box, + socket: labelledIcon, + start: icon, + sync: function (config, clas) { + clas.dividers = []; + clas.parts = []; + if (config.direction == 'LR') { + clas.width = config.lineWidth * 3; + clas.height = config.fontSize * 5; + } + else { + clas.width = config.fontSize * 5; + clas.height = config.lineWidth * 3; + } + }, + table: function (config, clas) { + if (clas.parts.length == 1) { + box(config, clas); + return; + } + const gridcells = clas.parts.slice(1); + const rows = [[]]; + function isRowBreak(e) { + return !e.lines.length && !e.nodes.length && !e.assocs.length; + } + function isRowFull(e) { + const current = last(rows); + return rows[0] != current && rows[0].length == current.length; + } + function isEnd(e) { + return e == last(gridcells); + } + for (const comp of gridcells) { + if (!isEnd(comp) && isRowBreak(comp) && last(rows).length) { + rows.push([]); + } + else if (isRowFull()) { + rows.push([comp]); + } + else { + last(rows).push(comp); + } + } + const header = clas.parts[0]; + const cellW = Math.max((header.width ?? 0) / rows[0].length, ...gridcells.map((e) => e.width ?? 0)); + const cellH = Math.max(...gridcells.map((e) => e.height ?? 0)); + clas.width = cellW * rows[0].length; + clas.height = (header.height ?? 0) + cellH * rows.length; + const hh = header.height ?? 0; + clas.dividers = [ + [ + { x: 0, y: header.height ?? 0 }, + { x: 0, y: header.height ?? 0 }, + ], + ...rows.map((e, i) => [ + { x: 0, y: hh + i * cellH }, + { x: clas.width ?? 0, y: hh + i * cellH }, + ]), + ...rows[0].map((e, i) => [ + { x: (i + 1) * cellW, y: hh }, + { x: (i + 1) * cellW, y: clas.height }, + ]), + ]; + header.x = 0; + header.y = 0; + header.width = clas.width; + for (let i = 0; i < rows.length; i++) { + for (let j = 0; j < rows[i].length; j++) { + const cell = rows[i][j]; + cell.x = j * cellW; + cell.y = hh + i * cellH; + cell.width = cellW; + } + } + clas.parts = clas.parts.filter((e) => !isRowBreak(e)); + }, + transceiver: box, + }; + const visualizers = { + actor: function (node, x, y, config, g) { + const a = config.padding / 2; + const yp = y + a * 4; + const faceCenter = { x: node.x, y: yp - a }; + g.circle(faceCenter, a).fillAndStroke(); + g.path([ + { x: node.x, y: yp }, + { x: node.x, y: yp + 2 * a }, + ]).stroke(); + g.path([ + { x: node.x - a, y: yp + a }, + { x: node.x + a, y: yp + a }, + ]).stroke(); + g.path([ + { x: node.x - a, y: yp + a + config.padding }, + { x: node.x, y: yp + config.padding }, + { x: node.x + a, y: yp + a + config.padding }, + ]).stroke(); + }, + class: function (node, x, y, config, g) { + g.rect(x, y, node.width, node.height).fillAndStroke(); + }, + database: function (node, x, y, config, g) { + const pad = config.padding; + const cy = y - pad / 2; + const pi = 3.1416; + g.rect(x, y + pad, node.width, node.height - pad * 2).fill(); + g.path([ + { x: x, y: cy + pad * 1.5 }, + { x: x, y: cy - pad * 0.5 + node.height }, + ]).stroke(); + g.path([ + { x: x + node.width, y: cy + pad * 1.5 }, + { x: x + node.width, y: cy - pad * 0.5 + node.height }, + ]).stroke(); + g.ellipse({ x: node.x, y: cy + pad * 1.5 }, node.width, pad * 1.5).fillAndStroke(); + g.ellipse({ x: node.x, y: cy - pad * 0.5 + node.height }, node.width, pad * 1.5, 0, pi).fillAndStroke(); + }, + ellipse: function (node, x, y, config, g) { + g.ellipse({ x: node.x, y: node.y }, node.width, node.height).fillAndStroke(); + }, + end: function (node, x, y, config, g) { + g.circle({ x: node.x, y: y + node.height / 2 }, node.height / 3).fillAndStroke(); + g.fillStyle(config.stroke); + g.circle({ x: node.x, y: y + node.height / 2 }, node.height / 3 - config.padding / 2).fill(); + }, + frame: function (node, x, y, config, g) { + g.rect(x, y, node.width, node.height).fillAndStroke(); + }, + hidden: function (node, x, y, config, g) { }, + input: function (node, x, y, config, g) { + g.circuit([ + { x: x + config.padding, y: y }, + { x: x + node.width, y: y }, + { x: x + node.width - config.padding, y: y + node.height }, + { x: x, y: y + node.height }, + ]).fillAndStroke(); + }, + lollipop: function (node, x, y, config, g) { + g.circle({ x: node.x, y: y + node.height / 2 }, node.height / 2.5).fillAndStroke(); + }, + none: function (node, x, y, config, g) { }, + note: function (node, x, y, config, g) { + g.circuit([ + { x: x, y: y }, + { x: x + node.width - config.padding, y: y }, + { x: x + node.width, y: y + config.padding }, + { x: x + node.width, y: y + node.height }, + { x: x, y: y + node.height }, + { x: x, y: y }, + ]).fillAndStroke(); + g.path([ + { x: x + node.width - config.padding, y: y }, + { x: x + node.width - config.padding, y: y + config.padding }, + { x: x + node.width, y: y + config.padding }, + ]).stroke(); + }, + package: function (node, x, y, config, g) { + const headHeight = node.parts[0].height ?? 0; + g.rect(x, y + headHeight, node.width, node.height - headHeight).fillAndStroke(); + const w = g.measureText(node.parts[0].lines[0]).width + 2 * config.padding; + g.circuit([ + { x: x, y: y + headHeight }, + { x: x, y: y }, + { x: x + w, y: y }, + { x: x + w, y: y + headHeight }, + ]).fillAndStroke(); + }, + pipe: function (node, x, y, config, g) { + const pad = config.padding; + const pi = 3.1416; + g.rect(x, y, node.width, node.height).fill(); + g.path([ + { x: x, y: y }, + { x: x + node.width, y: y }, + ]).stroke(); + g.path([ + { x: x, y: y + node.height }, + { x: x + node.width, y: y + node.height }, + ]).stroke(); + g.ellipse({ x: x + node.width, y: node.y }, pad * 1.5, node.height).fillAndStroke(); + g.ellipse({ x: x, y: node.y }, pad * 1.5, node.height, pi / 2, (pi * 3) / 2).fillAndStroke(); + }, + receiver: function (node, x, y, config, g) { + g.circuit([ + { x: x - config.padding, y: y }, + { x: x + node.width, y: y }, + { x: x + node.width, y: y + node.height }, + { x: x - config.padding, y: y + node.height }, + { x: x, y: y + node.height / 2 }, + ]).fillAndStroke(); + }, + rhomb: function (node, x, y, config, g) { + g.circuit([ + { x: node.x, y: y }, + { x: x + node.width, y: node.y }, + { x: node.x, y: y + node.height }, + { x: x, y: node.y }, + ]).fillAndStroke(); + }, + roundrect: function (node, x, y, config, g) { + const r = Math.min(config.padding * 2 * config.leading, node.height / 2); + g.roundRect(x, y, node.width, node.height, r).fillAndStroke(); + }, + sender: function (node, x, y, config, g) { + g.circuit([ + { x: x, y: y }, + { x: x + node.width - config.padding, y: y }, + { x: x + node.width, y: y + node.height / 2 }, + { x: x + node.width - config.padding, y: y + node.height }, + { x: x, y: y + node.height }, + ]).fillAndStroke(); + }, + socket: function (node, x, y, config, g) { + const from = config.direction === 'TB' ? Math.PI : Math.PI / 2; + const to = config.direction === 'TB' ? 2 * Math.PI : -Math.PI / 2; + g.ellipse({ x: node.x, y: node.y }, node.width, node.height, from, to).stroke(); + }, + start: function (node, x, y, config, g) { + g.fillStyle(config.stroke); + g.circle({ x: node.x, y: y + node.height / 2 }, node.height / 2.5).fill(); + }, + sync: function (node, x, y, config, g) { + g.fillStyle(config.stroke); + g.rect(x, y, node.width, node.height).fillAndStroke(); + }, + table: function (node, x, y, config, g) { + g.rect(x, y, node.width, node.height).fillAndStroke(); + }, + transceiver: function (node, x, y, config, g) { + g.circuit([ + { x: x - config.padding, y: y }, + { x: x + node.width - config.padding, y: y }, + { x: x + node.width, y: y + node.height / 2 }, + { x: x + node.width - config.padding, y: y + node.height }, + { x: x - config.padding, y: y + node.height }, + { x: x, y: y + node.height / 2 }, + ]).fillAndStroke(); + }, + }; + + function layout(measurer, config, ast) { + function measureLines(lines, fontWeight) { + if (!lines.length) + return { width: 0, height: config.padding }; + measurer.setFont(config.font, config.fontSize, fontWeight, 'normal'); + return { + width: Math.round(Math.max(...lines.map(measurer.textWidth)) + 2 * config.padding), + height: Math.round(measurer.textHeight() * lines.length + 2 * config.padding), + }; + } + function layoutCompartment(c, compartmentIndex, style) { + const textSize = measureLines(c.lines, compartmentIndex ? 'normal' : 'bold'); + if (!c.nodes.length && !c.assocs.length) { + const layoutedPart = c; + layoutedPart.width = textSize.width; + layoutedPart.height = textSize.height; + layoutedPart.offset = { x: config.padding, y: config.padding }; + return; + } + const styledConfig = { + ...config, + direction: style.direction ?? config.direction, + }; + const layoutedNodes = c.nodes; + const layoutedAssoc = c.assocs; + for (let i = 0; i < layoutedAssoc.length; i++) + layoutedAssoc[i].id = `${i}`; + for (const e of layoutedNodes) + layoutNode(e, styledConfig); + const g = new graphre.graphlib.Graph({ + multigraph: true, + }); + g.setGraph({ + rankdir: style.direction || config.direction, + nodesep: config.spacing, + edgesep: config.spacing, + ranksep: config.spacing, + acyclicer: config.acyclicer, + ranker: config.ranker, + }); + for (const e of layoutedNodes) { + g.setNode(e.id, { width: e.layoutWidth, height: e.layoutHeight }); + } + for (const r of layoutedAssoc) { + if (r.type.indexOf('_') > -1) { + g.setEdge(r.start, r.end, { minlen: 0 }, r.id); + } + else if ((config.gravity ?? 1) != 1) { + g.setEdge(r.start, r.end, { minlen: config.gravity }, r.id); + } + else { + g.setEdge(r.start, r.end, {}, r.id); + } + } + graphre.layout(g); + const rels = indexBy(c.assocs, 'id'); + const nodes = indexBy(c.nodes, 'id'); + for (const name of g.nodes()) { + const node = g.node(name); + nodes[name].x = node.x; + nodes[name].y = node.y; + } + let left = 0; + let right = 0; + let top = 0; + let bottom = 0; + for (const edgeObj of g.edges()) { + const edge = g.edge(edgeObj); + const start = nodes[edgeObj.v]; + const end = nodes[edgeObj.w]; + const rel = rels[edgeObj.name]; + rel.path = [start, ...edge.points, end].map(toPoint); + const startP = rel.path[1]; + const endP = rel.path[rel.path.length - 2]; + layoutLabel(rel.startLabel, startP, adjustQuadrant(quadrant(startP, start) ?? 4, start, end)); + layoutLabel(rel.endLabel, endP, adjustQuadrant(quadrant(endP, end) ?? 2, end, start)); + left = Math.min(left, rel.startLabel.x, rel.endLabel.x, ...edge.points.map((e) => e.x), ...edge.points.map((e) => e.x)); + right = Math.max(right, rel.startLabel.x + rel.startLabel.width, rel.endLabel.x + rel.endLabel.width, ...edge.points.map((e) => e.x)); + top = Math.min(top, rel.startLabel.y, rel.endLabel.y, ...edge.points.map((e) => e.y)); + bottom = Math.max(bottom, rel.startLabel.y + rel.startLabel.height, rel.endLabel.y + rel.endLabel.height, ...edge.points.map((e) => e.y)); + } + const graph = g.graph(); + const width = Math.max(graph.width + (left < 0 ? -left : 0), right - left); + const height = Math.max(graph.height + (top < 0 ? -top : 0), bottom - top); + const graphHeight = height ? height + 2 * config.gutter : 0; + const graphWidth = width ? width + 2 * config.gutter : 0; + const part = c; + part.width = Math.max(textSize.width, graphWidth) + 2 * config.padding; + part.height = textSize.height + graphHeight + config.padding; + part.offset = { x: config.padding - left, y: config.padding - top }; + } + function toPoint(o) { + return { x: o.x, y: o.y }; + } + function layoutLabel(label, point, quadrant) { + if (!label.text) { + label.width = 0; + label.height = 0; + label.x = point.x; + label.y = point.y; + } + else { + const fontSize = config.fontSize; + const lines = label.text.split('`'); + label.width = Math.max(...lines.map((l) => measurer.textWidth(l))); + label.height = fontSize * lines.length; + label.x = + point.x + (quadrant == 1 || quadrant == 4 ? config.padding : -label.width - config.padding); + label.y = + point.y + (quadrant == 3 || quadrant == 4 ? config.padding : -label.height - config.padding); + } + } + function quadrant(point, node) { + if (point.x < node.x && point.y < node.y) + return 1; + if (point.x > node.x && point.y < node.y) + return 2; + if (point.x > node.x && point.y > node.y) + return 3; + if (point.x < node.x && point.y > node.y) + return 4; + return undefined; + } + function adjustQuadrant(quadrant, point, opposite) { + if (opposite.x == point.x || opposite.y == point.y) + return quadrant; + const flipHorizontally = [4, 3, 2, 1]; + const flipVertically = [2, 1, 4, 3]; + const oppositeQuadrant = opposite.y < point.y ? (opposite.x < point.x ? 2 : 1) : opposite.x < point.x ? 3 : 4; + if (oppositeQuadrant === quadrant) { + if (config.direction === 'LR') + return flipHorizontally[quadrant - 1]; + if (config.direction === 'TB') + return flipVertically[quadrant - 1]; + } + return quadrant; + } + function layoutNode(node, config) { + const style = config.styles[node.type] || styles.class; + for (let i = 0; i < node.parts.length; i++) { + layoutCompartment(node.parts[i], i, style); + } + const visual = layouters[style.visual] ?? layouters.class; + visual(config, node); + node.layoutWidth = (node.width ?? 0) + 2 * config.edgeMargin; + node.layoutHeight = (node.height ?? 0) + 2 * config.edgeMargin; + } + const root = ast; + layoutCompartment(root, 0, styles.class); + return root; + } + + function extractDirectives(source) { + const directives = []; + for (const line of source.split('\n')) { + if (line[0] === '#') { + const [key, ...values] = line.slice(1).split(':'); + directives.push({ key, value: values.join(':').trim() }); + } + } + return directives; + } + function linearParse(source) { + let line = 1; + let lineStartIndex = 0; + let index = 0; + const directives = extractDirectives(source); + source = source.replace(/^[ \t]*\/\/[^\n]*/gm, '').replace(/^#[^\n]*/gm, ''); + if (source.trim() === '') + return { + root: { nodes: [], assocs: [], lines: [] }, + directives, + }; + const part = parsePart(); + if (index < source.length) + error('end of file', source[index]); + return { root: part, directives }; + function advanceLineCounter() { + line++; + lineStartIndex = index; + } + function addNode(nodes, node) { + const i = nodes.findIndex((e) => e.id === node.id); + if (i === -1) + nodes.push(node); + else if (nodes[i].parts.length < node.parts.length) + nodes[i] = node; + } + function parsePart() { + const nodes = []; + const assocs = []; + const lines = []; + while (index < source.length) { + let lastIndex = index; + discard(/ /); + if (source[index] === '\n') { + pop(); + advanceLineCounter(); + } + else if (source[index] === ';') { + pop(); + } + else if (source[index] == '|' || source[index] == ']') { + return { nodes, assocs, lines }; + } + else if (source[index] == '[') { + const extracted = parseNodesAndAssocs(); + for (const node of extracted.nodes) + addNode(nodes, node); + for (const assoc of extracted.assocs) + assocs.push(assoc); + } + else { + const text = parseLine().trim(); + if (text) + lines.push(text); + } + if (index === lastIndex) + throw new Error('Infinite loop'); + } + return { nodes, assocs, lines }; + } + function parseNodesAndAssocs() { + const nodes = []; + const assocs = []; + let node = parseNode(); + addNode(nodes, node); + while (index < source.length) { + let lastIndex = index; + discard(/ /); + if (isOneOf('\n', ']', '|', ';')) { + return { nodes, assocs }; + } + else { + const { association, target } = parseAssociation(node); + assocs.push(association); + addNode(nodes, target); + node = target; + } + if (index === lastIndex) + throw new Error('Infinite loop'); + } + return { nodes, assocs }; + } + function transformEscapes(char) { + if (char === 'n') + return '\n'; + return char; + } + function parseAssociation(fromNode) { + let startLabel = ''; + while (index < source.length) { + let lastIndex = index; + if (isOneOf('\\')) { + pop(); + startLabel += transformEscapes(pop()); + } + if (isOneOf('(o-', '(-', 'o<-', 'o-', '+-', '<:-', '<-', '-')) + break; + else if (isOneOf('[', ']', '|', '<', '>', ';')) + error('label', source[index]); + else + startLabel += pop(); + if (index === lastIndex) + throw new Error('Infinite loop'); + } + const assoc1 = consumeOneOf('(o', '(', 'o<', 'o', '+', '<:', '<', ''); + const assoc2 = consumeOneOf('--', '-/-', '-'); + const assoc3 = consumeOneOf('o)', 'o', '>o', '>', ')', '+', ':>', ''); + const endLabel = consumeOptional(/[^\[]/); + const target = parseNode(); + return { + association: { + type: `${assoc1}${assoc2}${assoc3}`, + start: fromNode.id, + end: target.id, + startLabel: { text: startLabel.trim() }, + endLabel: { text: endLabel.trim() }, + }, + target: target, + }; + } + function parseNode() { + index++; + let attr = {}; + let type = 'class'; + if (source[index] == '<') { + const meta = parseMeta(); + attr = meta.attr; + type = meta.type ?? 'class'; + } + const parts = [parsePart()]; + while (source[index] == '|') { + let lastIndex = index; + pop(); + parts.push(parsePart()); + if (lastIndex === index) + throw new Error('Infinite loop'); + } + if (source[index] == ']') { + pop(); + discard(/ /); + return { parts: parts, attr, id: attr.id ?? parts[0].lines[0], type }; + } + error(']', source[index]); + } + function parseLine() { + const chars = []; + while (index < source.length) { + let lastIndex = index; + if (source[index] === '\\') { + pop(); + chars.push(transformEscapes(pop())); + } + else if (source[index].match(/[\[\]|;\n]/)) { + break; + } + else { + chars.push(pop()); + } + if (lastIndex === index) + throw new Error('Infinite loop'); + } + return chars.join(''); + } + function parseMeta() { + index++; + const type = consume(/[a-zA-Z0-9_]/); + const char = pop(); + if (char == '>') + return { type, attr: {} }; + if (char != ' ') + error([' ', '>'], char); + return { type, attr: parseAttrs() }; + } + function parseAttrs() { + const key = consume(/[a-zA-Z0-9_]/); + const separator = pop(); + if (separator != '=') + error('=', separator); + const value = consume(/[^> ]/); + const char = pop(); + if (char == '>') + return { [key]: value }; + if (char == ' ') + return { [key]: value, ...parseAttrs() }; + error([' ', '>'], char); + } + function pop() { + const char = source[index]; + index++; + return char; + } + function discard(regex) { + while (source[index]?.match(regex)) + index++; + } + function consume(regex, optional) { + const start = index; + while (source[index]?.match(regex)) + index++; + const end = index; + if (!optional && start == end) + error(regex, source[index]); + return source.slice(start, end); + } + function consumeOptional(regex) { + return consume(regex, 'optional'); + } + function isOneOf(...patterns) { + for (const pattern of patterns) { + const token = source.slice(index, index + pattern.length); + if (token == pattern) { + return true; + } + } + return false; + } + function consumeOneOf(...patterns) { + for (const pattern of patterns) { + const token = source.slice(index, index + pattern.length); + if (token == pattern) { + index += pattern.length; + return pattern; + } + } + const maxPatternLength = Math.max(...patterns.map((e) => e.length)); + if (index + 1 >= source.length) + error(patterns, undefined); + else + error(patterns, source.slice(index + 1, maxPatternLength)); + } + function error(expected, actual) { + throw new ParseError(expected, actual, line, index - lineStartIndex); + } + } + function serializeValue(value) { + if (value == null) + return 'end of file'; + if (value instanceof RegExp) + return value.toString().slice(1, -1); + if (Array.isArray(value)) + return value.map(serializeValue).join(' or '); + return JSON.stringify(value); + } + class ParseError extends Error { + constructor(expected, actual, line, column) { + const exp = serializeValue(expected); + const act = serializeValue(actual); + super(`Parse error at line ${line} column ${column}, expected ${exp} but got ${act}`); + this.expected = exp; + this.actual = act; + this.line = line; + this.column = column; + } + } + + function parse(source) { + const { root, directives } = linearParse(source); + return { root, directives, config: getConfig(directives) }; + function directionToDagre(word) { + if (word == 'down') + return 'TB'; + if (word == 'right') + return 'LR'; + else + return 'TB'; + } + function parseRanker(word) { + if (word == 'network-simplex' || word == 'tight-tree' || word == 'longest-path') { + return word; + } + return 'network-simplex'; + } + function parseCustomStyle(styleDef) { + const floatingKeywords = styleDef.replace(/[a-z]*=[^ ]+/g, ''); + const titleDef = last(styleDef.match('title=([^ ]*)') || ['']); + const bodyDef = last(styleDef.match('body=([^ ]*)') || ['']); + return { + title: { + bold: titleDef.includes('bold') || floatingKeywords.includes('bold'), + underline: titleDef.includes('underline') || floatingKeywords.includes('underline'), + italic: titleDef.includes('italic') || floatingKeywords.includes('italic'), + center: !(titleDef.includes('left') || styleDef.includes('align=left')), + }, + body: { + bold: bodyDef.includes('bold'), + underline: bodyDef.includes('underline'), + italic: bodyDef.includes('italic'), + center: bodyDef.includes('center'), + }, + dashed: styleDef.includes('dashed'), + fill: last(styleDef.match('fill=([^ ]*)') || []), + stroke: last(styleDef.match('stroke=([^ ]*)') || []), + visual: (last(styleDef.match('visual=([^ ]*)') || []) || 'class'), + direction: directionToDagre(last(styleDef.match('direction=([^ ]*)') || [])), + }; + } + function getConfig(directives) { + const d = Object.fromEntries(directives.map((e) => [e.key, e.value])); + const userStyles = {}; + for (const key in d) { + if (key[0] != '.') + continue; + const styleDef = d[key]; + userStyles[key.substring(1)] = parseCustomStyle(styleDef); + } + return { + arrowSize: +d.arrowSize || 1, + bendSize: +d.bendSize || 0.3, + direction: directionToDagre(d.direction), + gutter: +d.gutter || 20, + edgeMargin: +d.edgeMargin || 0, + gravity: Math.round(+(d.gravity ?? 1)), + edges: d.edges == 'hard' ? 'hard' : 'rounded', + fill: (d.fill || '#eee8d5;#fdf6e3;#eee8d5;#fdf6e3').split(';'), + background: d.background || 'transparent', + fillArrows: d.fillArrows === 'true', + font: d.font || 'Helvetica', + fontSize: +d.fontSize || 12, + leading: +d.leading || 1.35, + lineWidth: +d.lineWidth || 3, + padding: +d.padding || 8, + spacing: +d.spacing || 40, + stroke: d.stroke || '#33322E', + title: d.title || '', + zoom: +d.zoom || 1, + acyclicer: d.acyclicer === 'greedy' ? 'greedy' : undefined, + ranker: parseRanker(d.ranker), + styles: { ...styles, ...userStyles }, + }; + } + } + + function add(a, b) { + return { x: a.x + b.x, y: a.y + b.y }; + } + function diff(a, b) { + return { x: a.x - b.x, y: a.y - b.y }; + } + function mult(v, factor) { + return { x: factor * v.x, y: factor * v.y }; + } + function mag(v) { + return Math.sqrt(v.x * v.x + v.y * v.y); + } + function normalize(v) { + return mult(v, 1 / mag(v)); + } + function rot(a) { + return { x: a.y, y: -a.x }; + } + + const empty = false; + const filled = true; + function getPath(config, r) { + const path = r.path.slice(1, -1); + const endDir = normalize(diff(path[path.length - 2], last(path))); + const startDir = normalize(diff(path[1], path[0])); + const size = (config.spacing * config.arrowSize) / 30; + const head = 0; + const end = path.length - 1; + const copy = path.map((p) => ({ x: p.x, y: p.y })); + const tokens = r.type.split(/[-_]/); + copy[head] = add(copy[head], mult(startDir, size * terminatorSize(tokens[0]))); + copy[end] = add(copy[end], mult(endDir, size * terminatorSize(last(tokens)))); + return copy; + } + function terminatorSize(id) { + if (id === '>' || id === '<') + return 5; + if (id === ':>' || id === '<:') + return 10; + if (id === '+') + return 14; + if (id === 'o') + return 14; + if (id === '(' || id === ')') + return 11; + if (id === '(o' || id === 'o)') + return 11; + if (id === '>o' || id === 'o<') + return 15; + return 0; + } + function drawTerminators(g, config, r) { + const start = r.path[1]; + const end = r.path[r.path.length - 2]; + const path = r.path.slice(1, -1); + const tokens = r.type.split(/[-_]/); + drawArrowEnd(last(tokens), path, end); + drawArrowEnd(tokens[0], path.reverse(), start); + function drawArrowEnd(id, path, end) { + const dir = normalize(diff(path[path.length - 2], last(path))); + const size = (config.spacing * config.arrowSize) / 30; + if (id === '>' || id === '<') + drawArrow(dir, size, filled, end); + else if (id === ':>' || id === '<:') + drawArrow(dir, size, empty, end); + else if (id === '+') + drawDiamond(dir, size, filled, end); + else if (id === 'o') + drawDiamond(dir, size, empty, end); + else if (id === '(' || id === ')') { + drawSocket(dir, size, 11, end); + drawStem(dir, size, 5, end); + } + else if (id === '(o' || id === 'o)') { + drawSocket(dir, size, 11, end); + drawStem(dir, size, 5, end); + drawBall(dir, size, 11, end); + } + else if (id === '>o' || id === 'o<') { + drawArrow(dir, size * 0.75, empty, add(end, mult(dir, size * 10))); + drawStem(dir, size, 8, end); + drawBall(dir, size, 8, end); + } + } + function drawBall(nv, size, stem, end) { + const center = add(end, mult(nv, size * stem)); + g.fillStyle(config.fill[0]); + g.ellipse(center, size * 6, size * 6).fillAndStroke(); + } + function drawStem(nv, size, stem, end) { + const center = add(end, mult(nv, size * stem)); + g.path([center, end]).stroke(); + } + function drawSocket(nv, size, stem, end) { + const base = add(end, mult(nv, size * stem)); + const t = rot(nv); + const socket = range([-Math.PI / 2, Math.PI / 2], 12).map((a) => add(base, add(mult(nv, -6 * size * Math.cos(a)), mult(t, 6 * size * Math.sin(a))))); + g.path(socket).stroke(); + } + function drawArrow(nv, size, isOpen, end) { + const x = (s) => add(end, mult(nv, s * size)); + const y = (s) => mult(rot(nv), s * size); + const arrow = [ + add(x(10), y(4)), + x(isOpen && !config.fillArrows ? 5 : 10), + add(x(10), y(-4)), + end, + ]; + g.fillStyle(isOpen ? config.stroke : config.fill[0]); + g.circuit(arrow).fillAndStroke(); + } + function drawDiamond(nv, size, isOpen, end) { + const x = (s) => add(end, mult(nv, s * size)); + const y = (s) => mult(rot(nv), s * size); + const arrow = [add(x(7), y(4)), x(14), add(x(7), y(-4)), end]; + g.save(); + g.fillStyle(isOpen ? config.stroke : config.fill[0]); + g.circuit(arrow).fillAndStroke(); + g.restore(); + } + } + + function render(graphics, config, compartment) { + const g = graphics; + function renderCompartment(compartment, color, style, level) { + g.save(); + g.translate(compartment.offset.x, compartment.offset.y); + g.fillStyle(color || config.stroke); + for (let i = 0; i < compartment.lines.length; i++) { + const text = compartment.lines[i]; + g.textAlign(style.center ? 'center' : 'left'); + const x = style.center ? compartment.width / 2 - config.padding : 0; + let y = (0.5 + (i + 0.5) * config.leading) * config.fontSize; + if (text) { + g.fillText(text, x, y); + } + if (style.underline) { + const w = g.measureText(text).width; + y += Math.round(config.fontSize * 0.2) + 0.5; + if (style.center) { + g.path([ + { x: x - w / 2, y: y }, + { x: x + w / 2, y: y }, + ]).stroke(); + } + else { + g.path([ + { x: x, y: y }, + { x: x + w, y: y }, + ]).stroke(); + } + g.lineWidth(config.lineWidth); + } + } + g.save(); + g.translate(config.gutter, config.gutter); + for (const r of compartment.assocs) + renderRelation(r); + for (const n of compartment.nodes) + renderNode(n, level); + g.restore(); + g.restore(); + } + function renderNode(node, level) { + const x = node.x - node.width / 2; + const y = node.y - node.height / 2; + const style = config.styles[node.type] || styles.class; + g.save(); + g.setData('name', node.id); + g.setData('compartment', undefined); + g.save(); + g.fillStyle(style.fill || config.fill[level] || last(config.fill)); + g.strokeStyle(style.stroke || config.stroke); + if (style.dashed) { + const dash = Math.max(4, 2 * config.lineWidth); + g.setLineDash([dash, dash]); + } + const drawNode = visualizers[style.visual] || visualizers.class; + drawNode(node, x, y, config, g); + for (const divider of node.dividers) { + g.path(divider.map((e) => add(e, { x, y }))).stroke(); + } + g.restore(); + let partIndex = 0; + for (let part of node.parts) { + const textStyle = part === node.parts[0] ? style.title : style.body; + g.save(); + g.setData('compartment', String(partIndex)); + g.translate(x + part.x, y + part.y); + g.setFont(config.font, config.fontSize, textStyle.bold ? 'bold' : 'normal', textStyle.italic ? 'italic' : 'normal'); + renderCompartment(part, style.stroke, textStyle, level + 1); + partIndex++; + g.restore(); + } + g.restore(); + } + function strokePath(p) { + if (config.edges === 'rounded') { + const radius = config.spacing * config.bendSize; + g.beginPath(); + g.moveTo(p[0].x, p[0].y); + for (let i = 1; i < p.length - 1; i++) { + g.arcTo(p[i].x, p[i].y, p[i + 1].x, p[i + 1].y, radius); + } + g.lineTo(last(p).x, last(p).y); + g.stroke(); + } + else + g.path(p).stroke(); + } + function renderLabel(label) { + if (!label || !label.text) + return; + const fontSize = config.fontSize; + const lines = label.text.split('`'); + for (let i = 0; i < lines.length; i++) { + g.fillText(lines[i], label.x, label.y + fontSize * (i + 1)); + } + } + function renderRelation(r) { + const path = getPath(config, r); + g.fillStyle(config.stroke); + g.setFont(config.font, config.fontSize, 'normal', 'normal'); + renderLabel(r.startLabel); + renderLabel(r.endLabel); + if (r.type !== '-/-') { + if (r.type.includes('--')) { + const dash = Math.max(4, 2 * config.lineWidth); + g.save(); + g.setLineDash([dash, dash]); + strokePath(path); + g.restore(); + } + else + strokePath(path); + } + drawTerminators(g, config, r); + } + function setBackground() { + g.clear(); + g.save(); + g.strokeStyle('transparent'); + g.fillStyle(config.background); + g.rect(0, 0, compartment.width, compartment.height).fill(); + g.restore(); + } + g.save(); + g.scale(config.zoom, config.zoom); + setBackground(); + g.setFont(config.font, config.fontSize, 'bold', 'normal'); + g.lineWidth(config.lineWidth); + g.lineJoin('round'); + g.lineCap('round'); + g.strokeStyle(config.stroke); + renderCompartment(compartment, undefined, buildStyle({}, {}).title, 0); + g.restore(); + } + + function GraphicsCanvas(canvas) { + const ctx = canvas.getContext('2d'); + const twopi = 2 * 3.1416; + let mousePos = { x: 0, y: 0 }; + const chainable = { + stroke: function () { + ctx.stroke(); + return chainable; + }, + fill: function () { + ctx.fill(); + return chainable; + }, + fillAndStroke: function () { + ctx.fill(); + ctx.stroke(); + return chainable; + }, + }; + function tracePath(path, offset, s) { + s = s === undefined ? 1 : s; + offset = offset || { x: 0, y: 0 }; + ctx.beginPath(); + ctx.moveTo(offset.x + s * path[0].x, offset.y + s * path[0].y); + for (let i = 1, len = path.length; i < len; i++) + ctx.lineTo(offset.x + s * path[i].x, offset.y + s * path[i].y); + return chainable; + } + return { + mousePos: function () { + return mousePos; + }, + width: function () { + return canvas.width; + }, + height: function () { + return canvas.height; + }, + clear: function () { + ctx.clearRect(0, 0, canvas.width, canvas.height); + }, + circle: function (p, r) { + ctx.beginPath(); + ctx.arc(p.x, p.y, r, 0, twopi); + return chainable; + }, + ellipse: function (center, rx, ry, start, stop) { + if (start === undefined) + start = 0; + if (stop === undefined) + stop = twopi; + ctx.beginPath(); + ctx.save(); + ctx.translate(center.x, center.y); + ctx.scale(1, ry / rx); + ctx.arc(0, 0, rx / 2, start, stop); + ctx.restore(); + return chainable; + }, + arc: function (x, y, r, start, stop) { + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.arc(x, y, r, start, stop); + return chainable; + }, + roundRect: function (x, y, w, h, r) { + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.arcTo(x + w, y, x + w, y + r, r); + ctx.lineTo(x + w, y + h - r); + ctx.arcTo(x + w, y + h, x + w - r, y + h, r); + ctx.lineTo(x + r, y + h); + ctx.arcTo(x, y + h, x, y + h - r, r); + ctx.lineTo(x, y + r); + ctx.arcTo(x, y, x + r, y, r); + ctx.closePath(); + return chainable; + }, + rect: function (x, y, w, h) { + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + w, y); + ctx.lineTo(x + w, y + h); + ctx.lineTo(x, y + h); + ctx.closePath(); + return chainable; + }, + path: tracePath, + circuit: function (path, offset, s) { + tracePath(path, offset, s); + ctx.closePath(); + return chainable; + }, + setFont: function (family, size, weight, style) { + ctx.font = `${weight} ${style} ${size}pt ${family}, Helvetica, sans-serif`; + }, + fillStyle: function (s) { + ctx.fillStyle = s; + }, + strokeStyle: function (s) { + ctx.strokeStyle = s; + }, + textAlign: function (a) { + ctx.textAlign = a; + }, + lineCap: function (cap) { + ctx.lineCap = cap; + }, + lineJoin: function (join) { + ctx.lineJoin = join; + }, + lineWidth: function (w) { + ctx.lineWidth = w; + }, + arcTo: function () { + return ctx.arcTo.apply(ctx, arguments); + }, + beginPath: function () { + return ctx.beginPath.apply(ctx, arguments); + }, + fillText: function () { + return ctx.fillText.apply(ctx, arguments); + }, + lineTo: function () { + return ctx.lineTo.apply(ctx, arguments); + }, + measureText: function () { + return ctx.measureText.apply(ctx, arguments); + }, + moveTo: function () { + return ctx.moveTo.apply(ctx, arguments); + }, + restore: function () { + return ctx.restore.apply(ctx, arguments); + }, + setData: function (name, value) { }, + save: function () { + return ctx.save.apply(ctx, arguments); + }, + scale: function () { + return ctx.scale.apply(ctx, arguments); + }, + setLineDash: function () { + return ctx.setLineDash.apply(ctx, arguments); + }, + stroke: function () { + return ctx.stroke.apply(ctx, arguments); + }, + translate: function () { + return ctx.translate.apply(ctx, arguments); + }, + }; + } + + function toAttrString(obj) { + return Object.entries(obj) + .filter(([_, val]) => val !== undefined) + .map(([key, val]) => `${key}="${xmlEncode(val)}"`) + .join(' '); + } + function xmlEncode(str) { + if ('number' === typeof str) + return str.toFixed(1); + return (str ?? '') + .toString() + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + const charWidths = { "0": 10, "1": 10, "2": 10, "3": 10, "4": 10, "5": 10, "6": 10, "7": 10, "8": 10, "9": 10, " ": 5, "!": 5, "\"": 6, "#": 10, "$": 10, "%": 15, "&": 11, "'": 4, "(": 6, ")": 6, "*": 7, "+": 10, ",": 5, "-": 6, ".": 5, "/": 5, ":": 5, ";": 5, "<": 10, "=": 10, ">": 10, "?": 10, "@": 17, "A": 11, "B": 11, "C": 12, "D": 12, "E": 11, "F": 10, "G": 13, "H": 12, "I": 5, "J": 9, "K": 11, "L": 10, "M": 14, "N": 12, "O": 13, "P": 11, "Q": 13, "R": 12, "S": 11, "T": 10, "U": 12, "V": 11, "W": 16, "X": 11, "Y": 11, "Z": 10, "[": 5, "\\": 5, "]": 5, "^": 8, "_": 10, "`": 6, "a": 10, "b": 10, "c": 9, "d": 10, "e": 10, "f": 5, "g": 10, "h": 10, "i": 4, "j": 4, "k": 9, "l": 4, "m": 14, "n": 10, "o": 10, "p": 10, "q": 10, "r": 6, "s": 9, "t": 5, "u": 10, "v": 9, "w": 12, "x": 9, "y": 9, "z": 9, "{": 6, "|": 5, "}": 6, "~": 10 }; + function GraphicsSvg(document) { + const initialState = { + stroke: undefined, + 'stroke-width': 1, + 'stroke-dasharray': undefined, + 'stroke-linecap': undefined, + 'stroke-linejoin': undefined, + 'text-align': 'left', + font: '12pt Helvetica, Arial, sans-serif', + 'font-size': '12pt', + }; + const measurementCanvas = document + ? document.createElement('canvas') + : null; + const ctx = measurementCanvas ? measurementCanvas.getContext('2d') : null; + class Element { + constructor(name, attr, parent, text) { + this.elideEmpty = false; + this.name = name; + this.attr = attr; + this.parent = parent; + this.children = []; + this.text = text || undefined; + } + stroke() { + this.attr.fill = 'none'; + return this; + } + fill() { + this.attr.stroke = 'none'; + return this; + } + fillAndStroke() { + return this; + } + group() { + return this.parent; + } + serialize() { + const data = getAncestorData(this.group()) ?? {}; + const attrs = toAttrString({ ...this.attr, ...data }); + const content = this.children.map((o) => o.serialize()).join('\n'); + if (this.text && this.children.length === 0) + return `<${this.name} ${attrs}>${xmlEncode(this.text)}`; + else if (this.children.length === 0) + return this.elideEmpty ? '' : `<${this.name} ${attrs}>`; + else + return `<${this.name} ${attrs}> + ${content.replace(/\n/g, '\n\t')} +`; + } + } + function getAncestorData(group) { + if (!group) + return syntheticRoot.data; + return { ...getAncestorData(group.parent), ...group.data }; + } + function getDefined(group, getter) { + if (!group) + return getter(syntheticRoot); + return getter(group) ?? getDefined(group.parent, getter) ?? getter(syntheticRoot); + } + class GroupElement extends Element { + constructor(parent) { + super('g', {}, parent); + this.elideEmpty = true; + } + group() { + return this; + } + } + const syntheticRoot = new GroupElement({}); + syntheticRoot.attr = initialState; + const root = new Element('svg', { + version: '1.1', + baseProfile: 'full', + xmlns: 'http://www.w3.org/2000/svg', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'xmlns:ev': 'http://www.w3.org/2001/xml-events', + }, undefined); + let current = new GroupElement(root); + current.attr = initialState; + root.children.push(current); + let inPathBuilderMode = false; + function tracePath(path, offset = { x: 0, y: 0 }, s = 1) { + const d = path + .map((e, i) => (i ? 'L' : 'M') + (offset.x + s * e.x).toFixed(1) + ' ' + (offset.y + s * e.y).toFixed(1)) + .join(' '); + return el('path', { d: d }); + } + function el(type, attr, text) { + const element = new Element(type, attr, current, text); + current.children.push(element); + return element; + } + return { + width: function () { + return 0; + }, + height: function () { + return 0; + }, + clear: function () { }, + circle: function (p, r) { + return el('circle', { r: r, cx: p.x, cy: p.y }); + }, + ellipse: function (center, w, h, start = 0, stop = 0) { + if (start || stop) { + const path = range([start, stop], 64).map((a) => add(center, { x: (Math.cos(a) * w) / 2, y: (Math.sin(a) * h) / 2 })); + return tracePath(path); + } + else { + return el('ellipse', { cx: center.x, cy: center.y, rx: w / 2, ry: h / 2 }); + } + }, + arc: function (cx, cy, r) { + return el('ellipse', { cx, cy, rx: r, ry: r }); + }, + roundRect: function (x, y, width, height, r) { + return el('rect', { x, y, rx: r, ry: r, height, width }); + }, + rect: function (x, y, width, height) { + return el('rect', { x, y, height, width }); + }, + path: tracePath, + circuit: function (path, offset, s) { + const element = tracePath(path, offset, s); + element.attr.d += ' Z'; + return element; + }, + setFont: function (family, size, weight, style) { + current.attr['font-family'] = family; + current.attr['font-size'] = size + 'pt'; + current.attr['font-weight'] = weight; + current.attr['font-style'] = style; + }, + strokeStyle: function (stroke) { + current.attr.stroke = stroke; + }, + fillStyle: function (fill) { + current.attr.fill = fill; + }, + arcTo: function (x1, y1, x2, y2) { + if (inPathBuilderMode) + last(current.children).attr.d += 'L' + x1 + ' ' + y1 + ' L' + x2 + ' ' + y2 + ' '; + else + throw new Error('can only be called after .beginPath()'); + }, + beginPath: function () { + inPathBuilderMode = true; + return el('path', { d: '' }); + }, + fillText: function (text, x, y) { + return el('text', { + x, + y, + stroke: 'none', + font: undefined, + style: undefined, + 'text-anchor': getDefined(current, (e) => e.attr['text-align']) === 'center' ? 'middle' : undefined, + }, text); + }, + lineCap: function (cap) { + current.attr['stroke-linecap'] = cap; + }, + lineJoin: function (join) { + current.attr['stroke-linejoin'] = join; + }, + lineTo: function (x, y) { + if (inPathBuilderMode) + last(current.children).attr.d += 'L' + x.toFixed(1) + ' ' + y.toFixed(1) + ' '; + else + throw new Error('can only be called after .beginPath()'); + return current; + }, + lineWidth: function (w) { + current.attr['stroke-width'] = w; + }, + measureText: function (s) { + if (ctx) { + if (current) + ctx.font = `${getDefined(current, (e) => e.attr['font-weight'])} ${getDefined(current, (e) => e.attr['font-style'])} ${getDefined(current, (e) => e.attr['font-size'])} ${getDefined(current, (e) => e.attr['font-family'])}`; + else + ctx.font = `${initialState['font-weight']} ${initialState['font-style']} ${initialState['font-size']} ${initialState['font-family']}`; + return ctx.measureText(s); + } + else { + return { + width: sum(s, function (c) { + const size = getDefined(current, (e) => e.attr['font-size']) ?? 12; + const scale = parseInt(size.toString()) / 12; + return (charWidths[c] ?? 16) * scale; + }), + }; + } + }, + moveTo: function (x, y) { + if (inPathBuilderMode) + last(current.children).attr.d += 'M' + x.toFixed(1) + ' ' + y.toFixed(1) + ' '; + else + throw new Error('can only be called after .beginPath()'); + }, + restore: function () { + if (current.parent) + current = current.parent; + }, + save: function () { + const node = new GroupElement(current); + current.children.push(node); + current = node; + }, + setData: function (name, value) { + current.data = current.data ?? {}; + current.data['data-' + name] = value; + }, + scale: function () { }, + setLineDash: function (d) { + current.attr['stroke-dasharray'] = d.length === 0 ? 'none' : d[0] + ' ' + d[1]; + }, + stroke: function () { + inPathBuilderMode = false; + last(current.children).stroke(); + }, + textAlign: function (a) { + current.attr['text-align'] = a; + }, + translate: function (dx, dy) { + if (Number.isNaN(dx) || Number.isNaN(dy)) { + throw new Error('dx and dy must be real numbers'); + } + current.attr.transform = `translate(${dx}, ${dy})`; + }, + serialize: function (size, desc, title) { + if (desc) { + root.children.unshift(new Element('desc', {}, undefined, desc)); + } + if (title) { + root.children.unshift(new Element('title', {}, undefined, title)); + } + root.attr = { + version: '1.1', + baseProfile: 'full', + width: size.width, + height: size.height, + viewBox: '0 0 ' + size.width + ' ' + size.height, + xmlns: 'http://www.w3.org/2000/svg', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'xmlns:ev': 'http://www.w3.org/2001/xml-events', + }; + return root.serialize(); + }, + }; + } + + function fitCanvasSize(canvas, rect, zoom) { + canvas.width = rect.width * zoom; + canvas.height = rect.height * zoom; + } + function createMeasurer(config, graphics) { + return { + setFont(family, size, weight, style) { + graphics.setFont(family, size, weight, style); + }, + textWidth(s) { + return graphics.measureText(s).width; + }, + textHeight() { + return config.leading * config.fontSize; + }, + }; + } + function parseAndRender(code, graphics, canvas, scale) { + const parsedDiagram = parse(code); + const config = parsedDiagram.config; + const measurer = createMeasurer(config, graphics); + const graphLayout = layout(measurer, config, parsedDiagram.root); + if (canvas) { + fitCanvasSize(canvas, graphLayout, config.zoom * scale); + } + config.zoom *= scale; + render(graphics, config, graphLayout); + return { config: config, layout: graphLayout }; + } + function draw(canvas, code, scale) { + return parseAndRender(code, GraphicsCanvas(canvas), canvas, scale || 1); + } + function renderSvg(code, document) { + const skCanvas = GraphicsSvg(document); + const { config, layout } = parseAndRender(code, skCanvas, null, 1); + return skCanvas.serialize({ + width: layout.width, + height: layout.height, + }, code, config.title); + } + class ImportDepthError extends Error { + constructor() { + super('max_import_depth exceeded'); + } + } + async function processAsyncImports(source, loadFile, maxImportDepth = 10) { + if (maxImportDepth == -1) { + throw new ImportDepthError(); + } + async function lenientLoadFile(key) { + try { + return (await loadFile(key)) || ''; + } + catch (e) { + return ''; + } + } + const imports = []; + source.replace(/#import: *(.*)/g, (a, file) => { + const promise = lenientLoadFile(file).then((contents) => processAsyncImports(contents, loadFile, maxImportDepth - 1)); + imports.push({ file, promise }); + return ''; + }); + const imported = {}; + for (const imp of imports) { + imported[imp.file] = await imp.promise; + } + return source.replace(/#import: *(.*)/g, (a, file) => imported[file]); + } + function processImports(source, loadFile, maxImportDepth = 10) { + if (maxImportDepth == -1) { + throw new ImportDepthError(); + } + function lenientLoadFile(key) { + try { + return loadFile(key) || ''; + } + catch (e) { + return ''; + } + } + return source.replace(/#import: *(.*)/g, (a, file) => processImports(lenientLoadFile(file), loadFile, maxImportDepth - 1)); + } + function compileFile(filepath, maxImportDepth) { + const fs = require('fs'); + const path = require('path'); + const directory = path.dirname(filepath); + const rootFileName = path.basename(filepath); + function loadFile(filename) { + return fs.readFileSync(path.join(directory, filename), { encoding: 'utf8' }); + } + return processImports(loadFile(rootFileName), loadFile, maxImportDepth); + } + + const version = '1.7.0'; + + exports.ImportDepthError = ImportDepthError; + exports.ParseError = ParseError; + exports.compileFile = compileFile; + exports.draw = draw; + exports.layout = layout; + exports.parse = parse; + exports.processAsyncImports = processAsyncImports; + exports.processImports = processImports; + exports.renderSvg = renderSvg; + exports.skanaar = util; + exports.styles = styles; + exports.version = version; + exports.visualizers = visualizers; + +})); From 411633fdc527de081f46424b4506d03045e7f55d Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sun, 1 Mar 2026 14:27:42 +0100 Subject: [PATCH 35/41] PrismJS rendering support --- Source/BookGen/Commands/Md2HtmlCommand.cs | 11 +- Source/BookGen/Commands/Md2TerminalCommand.cs | 3 +- Source/Bookgen.Lib/Bookgen.Lib.csproj | 1 - Source/Bookgen.Lib/BundledAssets.cs | 2 + .../ImageService/CachedImageService.cs | 12 --- .../Bookgen.Lib/ImageService/IImgService.cs | 1 - .../Bookgen.Lib/ImageService/ImageResult.cs | 1 + Source/Bookgen.Lib/ImageService/ImgService.cs | 28 ----- .../JsInterop/ImageRenderJsInterop.cs | 80 ++++++++++++++ .../Bookgen.Lib/Markdown/BookGenExtension.cs | 8 +- .../Bookgen.Lib/Markdown/MarkdownConverter.cs | 2 +- ...rSettings.cs => MarkdownRenderSettings.cs} | 8 +- .../Markdown/Renderers/SyntaxRenderer.cs | 29 ++++- .../Pipeline/Epub/CreateHtmlPages.cs | 9 +- .../Bookgen.Lib/Pipeline/Feed/CreateItems.cs | 5 +- .../PostProcess/RenderPagesForPostProcess.cs | 12 ++- .../Bookgen.Lib/Pipeline/Print/RenderPages.cs | 5 +- .../Pipeline/StaticWebsite/RenderIndexPage.cs | 4 +- .../StaticWebsite/RenderStaticPages.cs | 4 +- .../Pipeline/Wordpress/CreateWpPages.cs | 6 +- Source/Bookgen.Lib/packages.lock.json | 6 -- .../Bookgen.Tests/Lib/UT_MarkdownConverter.cs | 101 ++++++++++++++++-- 22 files changed, 247 insertions(+), 91 deletions(-) create mode 100644 Source/Bookgen.Lib/JsInterop/ImageRenderJsInterop.cs rename Source/Bookgen.Lib/Markdown/{RenderSettings.cs => MarkdownRenderSettings.cs} (83%) diff --git a/Source/BookGen/Commands/Md2HtmlCommand.cs b/Source/BookGen/Commands/Md2HtmlCommand.cs index 03753878..6cbef01c 100644 --- a/Source/BookGen/Commands/Md2HtmlCommand.cs +++ b/Source/BookGen/Commands/Md2HtmlCommand.cs @@ -123,20 +123,23 @@ public override int Execute(Md2HtmlArguments arguments, IReadOnlyList co if (!ValidateTemplate(pageTemplate)) return ExitCodes.GeneralError; - var imgService = new ImgService(inputFilesScope, _log, new ImageConfig + var imgConfig = new ImageConfig { SvgRecode = arguments.SvgPassthrough ? SvgRecodeOption.Passtrough : SvgRecodeOption.AsWebp, ImageQualityOnResize = 90, - }); + }; + + var imgService = new ImgService(inputFilesScope, _log, imgConfig); - using var settings = new RenderSettings(imgService) + using var settings = new MarkdownRenderSettings(imgService) { HostUrl = string.Empty, DeleteFirstH1 = false, CssClasses = new CssClasses(), OffsetHeadingsBy = 0, AutoEmbedSupportedLinks = !arguments.NoEmbed, - PrismJsInterop = arguments.NoSyntax ? null : new SyntaxRenderJsInterop(_assetSource) + PrismJsInterop = arguments.NoSyntax ? null : new SyntaxRenderJsInterop(_assetSource), + ImageRenderJsInterop = new ImageRenderJsInterop(_assetSource, imgConfig) }; using var markdownConverter = new MarkdownConverter(settings); diff --git a/Source/BookGen/Commands/Md2TerminalCommand.cs b/Source/BookGen/Commands/Md2TerminalCommand.cs index 4c20eabd..77f90523 100644 --- a/Source/BookGen/Commands/Md2TerminalCommand.cs +++ b/Source/BookGen/Commands/Md2TerminalCommand.cs @@ -72,13 +72,14 @@ public override int Execute(Arguments arguments, IReadOnlyList context) { (string md, _) = _fileSystem.ReadInputFiles(arguments.InputFiles); - using var settings = new RenderSettings(null!) + using var settings = new MarkdownRenderSettings(null!) { DeleteFirstH1 = false, AutoEmbedSupportedLinks = false, CssClasses = new CssClasses(), HostUrl = string.Empty, PrismJsInterop = null, + ImageRenderJsInterop = null! }; using var markdonwConverter = new MarkdownConverter(settings); diff --git a/Source/Bookgen.Lib/Bookgen.Lib.csproj b/Source/Bookgen.Lib/Bookgen.Lib.csproj index f04d0847..69f1c965 100644 --- a/Source/Bookgen.Lib/Bookgen.Lib.csproj +++ b/Source/Bookgen.Lib/Bookgen.Lib.csproj @@ -27,7 +27,6 @@ - diff --git a/Source/Bookgen.Lib/BundledAssets.cs b/Source/Bookgen.Lib/BundledAssets.cs index 9b4e5a89..56fea27c 100644 --- a/Source/Bookgen.Lib/BundledAssets.cs +++ b/Source/Bookgen.Lib/BundledAssets.cs @@ -17,5 +17,7 @@ public static class BundledAssets public const string MathJax = "mathjax-bundled.js"; public const string QrCodeJs = "qrcode.min.js"; public const string JsPageToc = "PageToc.js"; + public const string NomnomlJs = "nomnoml.js"; + public const string GraphreJs = "graphre.js"; } diff --git a/Source/Bookgen.Lib/ImageService/CachedImageService.cs b/Source/Bookgen.Lib/ImageService/CachedImageService.cs index 39b5bf31..726da337 100644 --- a/Source/Bookgen.Lib/ImageService/CachedImageService.cs +++ b/Source/Bookgen.Lib/ImageService/CachedImageService.cs @@ -39,18 +39,6 @@ private static ulong GetCacheKey(string str) return hash; } - - public ImageResult EncodeSvg(string svgData) - { - ulong cacheKey = GetCacheKey(svgData); - return _memoryCache.GetOrCreate(cacheKey, entry => - { - - entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(180)); - return _service.EncodeSvg(svgData); - })!; - } - public ImageResult GetImageEmbedData(string path) { ulong cacheKey = GetCacheKey(path); diff --git a/Source/Bookgen.Lib/ImageService/IImgService.cs b/Source/Bookgen.Lib/ImageService/IImgService.cs index 4717f45e..8d686632 100644 --- a/Source/Bookgen.Lib/ImageService/IImgService.cs +++ b/Source/Bookgen.Lib/ImageService/IImgService.cs @@ -8,5 +8,4 @@ namespace Bookgen.Lib.ImageService; public interface IImgService { ImageResult GetImageEmbedData(string filePath); - ImageResult EncodeSvg(string svgData); } diff --git a/Source/Bookgen.Lib/ImageService/ImageResult.cs b/Source/Bookgen.Lib/ImageService/ImageResult.cs index 4eb634b1..76564a6f 100644 --- a/Source/Bookgen.Lib/ImageService/ImageResult.cs +++ b/Source/Bookgen.Lib/ImageService/ImageResult.cs @@ -3,6 +3,7 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- + namespace Bookgen.Lib.ImageService; public sealed record class ImageResult diff --git a/Source/Bookgen.Lib/ImageService/ImgService.cs b/Source/Bookgen.Lib/ImageService/ImgService.cs index efaa663b..3da993a2 100644 --- a/Source/Bookgen.Lib/ImageService/ImgService.cs +++ b/Source/Bookgen.Lib/ImageService/ImgService.cs @@ -61,34 +61,6 @@ private static ImageType GetImateType(SvgRecodeOption recodeOption) }; } - public ImageResult EncodeSvg(string svgData) - { - if (_imageConfig.SvgRecode == SvgRecodeOption.Passtrough) - { - return new ImageResult - { - Data = svgData, - ImageType = ImageType.Svg, - OriginalName = string.Empty, - }; - } - - - - using SKData rendered = ImageUtils.RenderSvg(svgData, - _imageConfig.ResizeWith, - _imageConfig.ResizeHeight, - _imageConfig.SvgRecode); - - return new ImageResult - { - Data = Convert.ToBase64String(rendered.AsSpan()), - ImageType = GetImateType(_imageConfig.SvgRecode), - OriginalName = string.Empty - }; - - } - public ImageResult GetImageEmbedData(string filePath) { filePath = filePath.Replace("../", ""); diff --git a/Source/Bookgen.Lib/JsInterop/ImageRenderJsInterop.cs b/Source/Bookgen.Lib/JsInterop/ImageRenderJsInterop.cs new file mode 100644 index 00000000..c87e94e2 --- /dev/null +++ b/Source/Bookgen.Lib/JsInterop/ImageRenderJsInterop.cs @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2026 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using System.Diagnostics; + +using Bookgen.Lib.Domain.IO.Configuration; +using Bookgen.Lib.ImageService; + +using BookGen.Vfs; + +using SkiaSharp; + +namespace Bookgen.Lib.JsInterop; + +public sealed class ImageRenderJsInterop : JavascriptInterop +{ + private readonly IAssetSource _assetSource; + private readonly ImageConfig _imageConfig; + private bool _nomnomlLoaded; + + public ImageRenderJsInterop(IAssetSource assetSource, ImageConfig imageConfig) + { + _assetSource = assetSource; + _imageConfig = imageConfig; + } + + private static ImageType GetImateType(SvgRecodeOption recodeOption) + { + return recodeOption switch + { + SvgRecodeOption.AsPng => ImageType.Png, + SvgRecodeOption.AsWebp => ImageType.Webp, + SvgRecodeOption.Passtrough => ImageType.Svg, + _ => throw new UnreachableException(), + }; + } + + private ImageResult EncodeSvg(string svgData) + { + if (_imageConfig.SvgRecode == SvgRecodeOption.Passtrough) + { + return new ImageResult + { + Data = svgData, + ImageType = ImageType.Svg, + OriginalName = string.Empty, + }; + } + + + + using SKData rendered = ImageUtils.RenderSvg(svgData, + _imageConfig.ResizeWith, + _imageConfig.ResizeHeight, + _imageConfig.SvgRecode); + + return new ImageResult + { + Data = Convert.ToBase64String(rendered.AsSpan()), + ImageType = GetImateType(_imageConfig.SvgRecode), + OriginalName = string.Empty + }; + } + + public ImageResult RenderNomnoml(string nomnomlCode) + { + if (!_nomnomlLoaded) + { + Execute(_assetSource.GetAsset(BundledAssets.GraphreJs)); + Execute(_assetSource.GetAsset(BundledAssets.NomnomlJs)); + _nomnomlLoaded = true; + } + + _engine.Script.nomnomlCode = nomnomlCode; + string svg = ExecuteAndGetResult("nomnoml.renderSvg(nomnomlCode)"); + return EncodeSvg(svg); + } +} diff --git a/Source/Bookgen.Lib/Markdown/BookGenExtension.cs b/Source/Bookgen.Lib/Markdown/BookGenExtension.cs index 87299ff6..20ac271c 100644 --- a/Source/Bookgen.Lib/Markdown/BookGenExtension.cs +++ b/Source/Bookgen.Lib/Markdown/BookGenExtension.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -20,13 +20,13 @@ namespace Bookgen.Lib.Markdown; internal sealed partial class BookGenExtension : IMarkdownExtension, IDisposable { - private RenderSettings? _settings; + private MarkdownRenderSettings? _settings; private MarkdownPipelineBuilder? _pipeline; [GeneratedRegex("^(\\w)+://")] private static partial Regex ProtocollRegex(); - public void Inject(RenderSettings settings) + public void Inject(MarkdownRenderSettings settings) { _settings = settings; } @@ -64,7 +64,7 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) if (codeBlockRenderer != null) { htmlRenderer.ObjectRenderers.Remove(codeBlockRenderer); - htmlRenderer.ObjectRenderers.AddIfNotAlready(new SyntaxRenderer(codeBlockRenderer, _settings.PrismJsInterop)); + htmlRenderer.ObjectRenderers.AddIfNotAlready(new SyntaxRenderer(codeBlockRenderer, _settings.PrismJsInterop, _settings.ImageRenderJsInterop)); } } diff --git a/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs b/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs index e51abc2e..2033e891 100644 --- a/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs +++ b/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs @@ -17,7 +17,7 @@ public sealed class MarkdownConverter : IDisposable private readonly MarkdownPipeline _htmlPipeLine; private readonly MarkdownPipeline _terminalPipeLine; - public MarkdownConverter(RenderSettings settings) + public MarkdownConverter(MarkdownRenderSettings settings) { MarkdownPipelineBuilder configuration = new MarkdownPipelineBuilder() .UseAdvancedExtensions() diff --git a/Source/Bookgen.Lib/Markdown/RenderSettings.cs b/Source/Bookgen.Lib/Markdown/MarkdownRenderSettings.cs similarity index 83% rename from Source/Bookgen.Lib/Markdown/RenderSettings.cs rename to Source/Bookgen.Lib/Markdown/MarkdownRenderSettings.cs index b6c9f0b6..a1c4320d 100644 --- a/Source/Bookgen.Lib/Markdown/RenderSettings.cs +++ b/Source/Bookgen.Lib/Markdown/MarkdownRenderSettings.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -9,12 +9,13 @@ namespace Bookgen.Lib.Markdown; -public sealed class RenderSettings : IDisposable +public sealed class MarkdownRenderSettings : IDisposable { private readonly IImgService _imgService; public required string? HostUrl { get; init; } public required SyntaxRenderJsInterop? PrismJsInterop { get; init; } + public required ImageRenderJsInterop ImageRenderJsInterop { get; init; } public required CssClasses CssClasses { get; init; } public required bool DeleteFirstH1 { get; init; } public int OffsetHeadingsBy { get; init; } = 0; @@ -36,7 +37,7 @@ private string EmbedImage(ImageResult arg) : $"data:{arg.ImageType.GetMimeType()};base64,{arg.Data}"; } - public RenderSettings(IImgService imgService) + public MarkdownRenderSettings(IImgService imgService) { ImageUrlRewriter = EmbedImage; _imgService = imgService; @@ -45,5 +46,6 @@ public RenderSettings(IImgService imgService) public void Dispose() { PrismJsInterop?.Dispose(); + ImageRenderJsInterop.Dispose(); } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs index e72f8548..ffb5542b 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs @@ -6,6 +6,7 @@ using System.Text; using System.Web; +using Bookgen.Lib.ImageService; using Bookgen.Lib.JsInterop; using Markdig.Helpers; @@ -20,8 +21,10 @@ internal sealed class SyntaxRenderer : HtmlObjectRenderer, IDisposabl { private readonly CodeBlockRenderer _originalRenderer; private readonly SyntaxRenderJsInterop? _prism; + private readonly ImageRenderJsInterop _imageRenderJsInterop; private readonly HashSet _supportedLanguages; - public const string Terminallanguage = "terminal"; + public const string TerminalLanguage = "terminal"; + public const string NomnomlLanguage = "nomnoml"; public const string TerminalHtml = """
    @@ -32,10 +35,13 @@ internal sealed class SyntaxRenderer : HtmlObjectRenderer, IDisposabl public bool PreRender => _prism != null; - public SyntaxRenderer(CodeBlockRenderer underlyingRenderer, SyntaxRenderJsInterop? prism) + public SyntaxRenderer(CodeBlockRenderer underlyingRenderer, + SyntaxRenderJsInterop? prism, + ImageRenderJsInterop imageRenderJsInterop) { _originalRenderer = underlyingRenderer ?? new CodeBlockRenderer(); _prism = prism; + _imageRenderJsInterop = imageRenderJsInterop; _supportedLanguages = new HashSet { "markup", "css", "clike", "javascript", "abap", "actionscript", @@ -58,7 +64,7 @@ public SyntaxRenderer(CodeBlockRenderer underlyingRenderer, SyntaxRenderJsIntero "sass", "scss", "scala", "scheme", "smalltalk", "smarty", "sql", "soy", "stylus", "swift", "tap", "tcl", "textile", "tt2", "twig", "typescript", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "wasm", "wiki", - "xeora", "xojo", "xquery", "yaml", Terminallanguage + "xeora", "xojo", "xquery", "yaml", TerminalLanguage, NomnomlLanguage }; } @@ -111,11 +117,19 @@ protected override void Write(HtmlRenderer renderer, CodeBlock obj) string code = GetCode(obj); - if (languageMoniker == Terminallanguage) + if (languageMoniker == TerminalLanguage) { string terminalOutput = RenderTerminalString(code); renderer.Write(terminalOutput); } + + if (languageMoniker == NomnomlLanguage) + { + ImageResult img = _imageRenderJsInterop.RenderNomnoml(code); + string rendered = RendererImgage(img); + renderer.Write(rendered); + } + else if (PreRender) { string rendered = RenderWithPrism(code, languageMoniker); @@ -127,6 +141,13 @@ protected override void Write(HtmlRenderer renderer, CodeBlock obj) } } + private static string RendererImgage(ImageResult img) + { + return img.ImageType == ImageType.Svg + ? img.Data + : $""; + } + public static string RenderTerminalString(string code) { const string codeTag = ""; diff --git a/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs b/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs index ab21b7b7..e5261332 100644 --- a/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Epub/CreateHtmlPages.cs @@ -40,17 +40,19 @@ private string EpubImageRewrite(ImageResult result) public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { - var imgService = new ImgService(environment.Source, logger, new ImageConfig + var imgConfig = new ImageConfig { SvgRecode = SvgRecodeOption.AsWebp, ResizeAndRecodeImages = ImgRecodeOption.AsPng, ImageQualityOnResize = 90, ResizeWith = 1600, ResizeHeight = 1600, - }); + }; + + var imgService = new ImgService(environment.Source, logger, imgConfig); var cached = new CachedImageService(imgService, _memoryCache); - using var settings = new RenderSettings(cached) + using var settings = new MarkdownRenderSettings(cached) { CssClasses = environment.Configuration.PrintConfig.CssClasses, DeleteFirstH1 = false, @@ -58,6 +60,7 @@ public override async Task ExecuteAsync(IBookEnvironment environment PrismJsInterop = new SyntaxRenderJsInterop(environment), OffsetHeadingsBy = 0, AutoEmbedSupportedLinks = false, + ImageRenderJsInterop = new ImageRenderJsInterop(environment, imgConfig), ImageUrlRewriter = EpubImageRewrite }; diff --git a/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs b/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs index efd1f88d..5471cc5c 100644 --- a/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs +++ b/Source/Bookgen.Lib/Pipeline/Feed/CreateItems.cs @@ -32,14 +32,15 @@ public override async Task ExecuteAsync(IBookEnvironment environment var imgService = new ImgService(environment.Source, logger, environment.Configuration.FeedConfig.Images); var cached = new CachedImageService(imgService, _memoryCache); - using var settings = new RenderSettings(cached) + using var settings = new MarkdownRenderSettings(cached) { CssClasses = environment.Configuration.FeedConfig.CssClasses, DeleteFirstH1 = false, HostUrl = string.Empty, PrismJsInterop = environment.Configuration.FeedConfig.PreRenderCode ? new SyntaxRenderJsInterop(environment) : null, OffsetHeadingsBy = 0, - AutoEmbedSupportedLinks = false + AutoEmbedSupportedLinks = false, + ImageRenderJsInterop = new ImageRenderJsInterop(environment, environment.Configuration.FeedConfig.Images) }; using var markdown = new MarkdownConverter(settings); diff --git a/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs b/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs index 2a040301..c1324df3 100644 --- a/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs +++ b/Source/Bookgen.Lib/Pipeline/PostProcess/RenderPagesForPostProcess.cs @@ -28,21 +28,25 @@ public RenderPagesForPostProcess(PostProcessState state, IMemoryCache memoryCach public override async Task ExecuteAsync(IBookEnvironment environment, ILogger logger) { - var imgService = new ImgService(environment.Source, logger, new ImageConfig() + var imgConfig = new ImageConfig() { ResizeAndRecodeImages = ImgRecodeOption.Passtrough, SvgRecode = SvgRecodeOption.Passtrough, - }); + }; + + var imgService = new ImgService(environment.Source, logger, imgConfig); + var cached = new CachedImageService(imgService, _memoryCache); - using var settings = new RenderSettings(cached) + using var settings = new MarkdownRenderSettings(cached) { CssClasses = environment.Configuration.PrintConfig.CssClasses, DeleteFirstH1 = false, HostUrl = string.Empty, PrismJsInterop = new SyntaxRenderJsInterop(environment), OffsetHeadingsBy = 1, - AutoEmbedSupportedLinks = false + AutoEmbedSupportedLinks = false, + ImageRenderJsInterop = new ImageRenderJsInterop(environment, imgConfig) }; using var markdown = new MarkdownConverter(settings); diff --git a/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs b/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs index ce9460cc..8bf2d6b5 100644 --- a/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Print/RenderPages.cs @@ -30,14 +30,15 @@ public override async Task ExecuteAsync(IBookEnvironment environment var imgService = new ImgService(environment.Source, logger, environment.Configuration.PrintConfig.Images); var cached = new CachedImageService(imgService, _memoryCache); - using var settings = new RenderSettings(cached) + using var settings = new MarkdownRenderSettings(cached) { CssClasses = environment.Configuration.PrintConfig.CssClasses, DeleteFirstH1 = false, HostUrl = string.Empty, PrismJsInterop = new SyntaxRenderJsInterop(environment), OffsetHeadingsBy = 1, - AutoEmbedSupportedLinks = false + AutoEmbedSupportedLinks = false, + ImageRenderJsInterop = new ImageRenderJsInterop(environment, environment.Configuration.PrintConfig.Images) }; using var markdown = new MarkdownConverter(settings); diff --git a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderIndexPage.cs b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderIndexPage.cs index 161760fa..c95c12cd 100644 --- a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderIndexPage.cs +++ b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderIndexPage.cs @@ -7,6 +7,7 @@ using Bookgen.Lib.Domain; using Bookgen.Lib.ImageService; +using Bookgen.Lib.JsInterop; using Bookgen.Lib.Markdown; using Bookgen.Lib.Templates; @@ -32,13 +33,14 @@ public override async Task ExecuteAsync(IBookEnvironment environment var cached = new CachedImageService(imgService, _memoryCache); var renderer = new TemplateEngine(logger, environment); - using var settings = new RenderSettings(cached) + using var settings = new MarkdownRenderSettings(cached) { CssClasses = environment.Configuration.StaticWebsiteConfig.CssClasses, DeleteFirstH1 = false, HostUrl = environment.Configuration.StaticWebsiteConfig.DeployHost, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(environment, environment.Configuration.StaticWebsiteConfig.Images) }; using var markdown = new MarkdownConverter(settings); diff --git a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderStaticPages.cs b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderStaticPages.cs index 3161e749..04f34271 100644 --- a/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderStaticPages.cs +++ b/Source/Bookgen.Lib/Pipeline/StaticWebsite/RenderStaticPages.cs @@ -7,6 +7,7 @@ using Bookgen.Lib.Domain; using Bookgen.Lib.ImageService; +using Bookgen.Lib.JsInterop; using Bookgen.Lib.Markdown; using Bookgen.Lib.Templates; @@ -32,13 +33,14 @@ public override async Task ExecuteAsync(IBookEnvironment environment var cached = new CachedImageService(imgService, _memoryCache); var renderer = new TemplateEngine(logger, environment); - using var settings = new RenderSettings(cached) + using var settings = new MarkdownRenderSettings(cached) { CssClasses = environment.Configuration.StaticWebsiteConfig.CssClasses, DeleteFirstH1 = false, HostUrl = environment.Configuration.StaticWebsiteConfig.DeployHost, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(environment, environment.Configuration.StaticWebsiteConfig.Images) }; ParallelOptions options = new ParallelOptions diff --git a/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs b/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs index 36e4fad2..45b620a7 100644 --- a/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs +++ b/Source/Bookgen.Lib/Pipeline/Wordpress/CreateWpPages.cs @@ -12,6 +12,7 @@ using Bookgen.Lib.Domain.Wordpress; using Bookgen.Lib.ImageService; using Bookgen.Lib.Internals; +using Bookgen.Lib.JsInterop; using Bookgen.Lib.Markdown; using Bookgen.Lib.Templates; @@ -119,17 +120,18 @@ public override async Task ExecuteAsync(IBookEnvironment environment { logger.LogInformation("Creating pages..."); - var imgService = new ImgService(environment.Source, logger, environment.Configuration.StaticWebsiteConfig.Images); + var imgService = new ImgService(environment.Source, logger, environment.Configuration.WordpressConfig.Images); var cached = new CachedImageService(imgService, _memoryCache); var renderer = new TemplateEngine(logger, environment); - using var settings = new RenderSettings(cached) + using var settings = new MarkdownRenderSettings(cached) { CssClasses = environment.Configuration.WordpressConfig.CssClasses, DeleteFirstH1 = false, HostUrl = environment.Configuration.WordpressConfig.DeployHost, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(environment, environment.Configuration.WordpressConfig.Images) }; using var markdown = new MarkdownConverter(settings); diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index e5d49517..9f69682a 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -33,12 +33,6 @@ "resolved": "7.5.0", "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" }, - "Microsoft.Extensions.Caching.Abstractions": { - "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==" - }, "SkiaSharp": { "type": "Direct", "requested": "[3.119.2, )", diff --git a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs index f2a377f8..5519f807 100644 --- a/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs +++ b/Test/Bookgen.Tests/Lib/UT_MarkdownConverter.cs @@ -5,6 +5,7 @@ using Bookgen.Lib.Domain.IO.Configuration; using Bookgen.Lib.ImageService; +using Bookgen.Lib.JsInterop; using Bookgen.Lib.Markdown; using Moq; @@ -17,10 +18,12 @@ internal class UT_MarkdownConverter private string _markdown; private string _soruceCode; private readonly IEqualityComparer comparer = new LineEndingIgnoreComparer(); + private TestEnvironment _testEnvironment; [SetUp] public void Setup() { + _testEnvironment = new TestEnvironment(); _imgServiceMock = new Mock(MockBehavior.Strict); _imgServiceMock.Setup(x => x.GetImageEmbedData("img.svg")).Returns(new ImageResult { @@ -67,10 +70,16 @@ This is a [link](https://youtube.be) """; } + [TearDown] + public void TearDown() + { + _testEnvironment.Dispose(); + } + [Test] public void EnsureThat_Css_ClassesAreAplied() { - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses { @@ -82,6 +91,7 @@ public void EnsureThat_Css_ClassesAreAplied() HostUrl = null, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -111,13 +121,14 @@ public void EnsureThat_Css_ClassesAreAplied() [Test] public void EnsureThat_DeleteFirstH1_HostUrlTargeting_Works() { - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses(), DeleteFirstH1 = true, HostUrl = "https://my.domain", PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -146,13 +157,14 @@ public void EnsureThat_DeleteFirstH1_HostUrlTargeting_Works() [Test] public void EnsureThat_SourceCode_Works() { - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses(), DeleteFirstH1 = false, HostUrl = "https://my.domain", PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -173,14 +185,14 @@ public void EnsureThat_SourceCode_Works() [Test] public void EnsureThat_SourceCode_PreRender_Works() { - using var testEnvironment = new TestEnvironment(); - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses(), DeleteFirstH1 = false, HostUrl = "https://my.domain", - PrismJsInterop = new Bookgen.Lib.JsInterop.SyntaxRenderJsInterop(testEnvironment), + PrismJsInterop = new Bookgen.Lib.JsInterop.SyntaxRenderJsInterop(_testEnvironment), AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -200,13 +212,14 @@ public void EnsureThat_SourceCode_PreRender_Works() [Test] public void EnsureThat_Toc_NormalCase_RendersCorrectly() { - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses(), DeleteFirstH1 = false, HostUrl = null, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -316,13 +329,14 @@ public void EnsureThat_Toc_NormalCase_RendersCorrectly() [Test] public void EnsureThat_Toc_WithTitle_RendersCorrectly() { - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses(), DeleteFirstH1 = false, HostUrl = null, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -436,13 +450,14 @@ [toc] TOC*-*Title [Test] public void EnsureThat_Toc_Limited_RendersCorrectly() { - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses(), DeleteFirstH1 = false, HostUrl = null, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -543,13 +558,14 @@ public void EnsureThat_Toc_Limited_RendersCorrectly() [Test] public void EnsureThat_TerminalRenderingWorks() { - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses(), DeleteFirstH1 = false, HostUrl = null, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -584,13 +600,14 @@ public void EnsrureThat_Youtube_Autolink_Works() """; - using var settings = new RenderSettings(_imgServiceMock.Object) + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) { CssClasses = new CssClasses(), DeleteFirstH1 = false, HostUrl = null, PrismJsInterop = null, AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig()) }; using var sut = new MarkdownConverter(settings); @@ -599,4 +616,66 @@ public void EnsrureThat_Youtube_Autolink_Works() Assert.That(result, Is.EqualTo(expected).Using(comparer)); } + + [Test] + public void EnsureThat_NomnomlRender_PngWorks() + { + string input = """ + ```nomnoml + [Foo|a;b;c] + ``` + """; + + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) + { + CssClasses = new CssClasses(), + DeleteFirstH1 = false, + HostUrl = null, + PrismJsInterop = null, + AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig + { + SvgRecode = SvgRecodeOption.AsPng + }) + }; + + using var sut = new MarkdownConverter(settings); + + string result = sut.RenderMarkdownToHtml(input); + + Assert.That(result, Does.StartWith("Foo|a;b;c] + ``` + """; + + using var settings = new MarkdownRenderSettings(_imgServiceMock.Object) + { + CssClasses = new CssClasses(), + DeleteFirstH1 = false, + HostUrl = null, + PrismJsInterop = null, + AutoEmbedSupportedLinks = true, + ImageRenderJsInterop = new ImageRenderJsInterop(_testEnvironment, new ImageConfig + { + SvgRecode = SvgRecodeOption.Passtrough + }) + }; + + using var sut = new MarkdownConverter(settings); + + string result = sut.RenderMarkdownToHtml(input); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result, Does.StartWith("")); + } + } } From f3634fafe047a2de043c32cff0efacb12b80ef1b Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Sun, 1 Mar 2026 14:38:12 +0100 Subject: [PATCH 36/41] Syntax renderer: logic fix --- .../Markdown/Renderers/SyntaxRenderer.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs index ffb5542b..b59bc5b0 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/SyntaxRenderer.cs @@ -117,33 +117,31 @@ protected override void Write(HtmlRenderer renderer, CodeBlock obj) string code = GetCode(obj); - if (languageMoniker == TerminalLanguage) + switch (languageMoniker) { - string terminalOutput = RenderTerminalString(code); - renderer.Write(terminalOutput); - } - - if (languageMoniker == NomnomlLanguage) - { - ImageResult img = _imageRenderJsInterop.RenderNomnoml(code); - string rendered = RendererImgage(img); - renderer.Write(rendered); - } - - else if (PreRender) - { - string rendered = RenderWithPrism(code, languageMoniker); - renderer.Write(rendered); - } - else - { - _originalRenderer.Write(renderer, obj); + case TerminalLanguage: + renderer.Write(RenderTerminalString(code)); + break; + case NomnomlLanguage: + ImageResult img = _imageRenderJsInterop.RenderNomnoml(code); + renderer.Write(RendererImgage(img)); + break; + default: + if (PreRender) + { + renderer.Write(RenderWithPrism(code, languageMoniker)); + } + else + { + _originalRenderer.Write(renderer, obj); + } + break; } } private static string RendererImgage(ImageResult img) { - return img.ImageType == ImageType.Svg + return img.ImageType == ImageType.Svg ? img.Data : $""; } From cb4a68cac1841788bcd697e337d130e4dbaf5c31 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Wed, 4 Mar 2026 17:36:42 +0100 Subject: [PATCH 37/41] ImgConvert command bugfixes --- Source/BookGen/Commands/ImgConvert.cs | 19 +++++++++++++------ .../ImageService/ImageConverter.cs | 11 +++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Source/BookGen/Commands/ImgConvert.cs b/Source/BookGen/Commands/ImgConvert.cs index 2d213ac9..ce488b19 100644 --- a/Source/BookGen/Commands/ImgConvert.cs +++ b/Source/BookGen/Commands/ImgConvert.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -95,7 +95,14 @@ public override int Execute(ImgConvertArgs arguments, IReadOnlyList cont ".jpg", ".jpeg", ".png", ".webp" }; - if (!Resolution.TryParse(arguments.Resolution, CultureInfo.InvariantCulture, out Resolution resolution)) + var maxResolution = new Resolution + { + Width = int.MaxValue, + Height = int.MaxValue + }; + + if (!string.IsNullOrEmpty(arguments.Resolution) + && !Resolution.TryParse(arguments.Resolution, CultureInfo.InvariantCulture, out maxResolution)) { Console.Error.WriteLine($"Invalid resolution format: '{arguments.Resolution}'. Expected format is 'WidthxHeight'."); return ExitCodes.ArgumentsError; @@ -109,18 +116,18 @@ public override int Execute(ImgConvertArgs arguments, IReadOnlyList cont Parallel.ForEach(files, file => { var outputFile = Path.Combine(arguments.Output, Path.GetFileNameWithoutExtension(file) + "." + arguments.Format); - ConvertImage(file, outputFile, format, arguments.Quality, resolution); + ConvertImage(file, outputFile, format, arguments.Quality, maxResolution); }); } else { - ConvertImage(arguments.Input, arguments.Output, format, arguments.Quality, resolution); + ConvertImage(arguments.Input, arguments.Output, format, arguments.Quality, maxResolution); } return ExitCodes.Success; } - private static void ConvertImage(string file, string outputFile, ImageFormat format, int quality, Resolution resolution) + private static void ConvertImage(string file, string outputFile, ImageFormat format, int quality, Resolution maxResolution) { ImageConverter.Encode(file, outputFile, format switch { @@ -128,6 +135,6 @@ private static void ConvertImage(string file, string outputFile, ImageFormat for ImageFormat.Png => ImageType.Png, ImageFormat.Webp => ImageType.Webp, _ => throw new NotSupportedException($"Image format {format} is not supported.") - }, resolution.Width, resolution.Height, quality); + }, maxResolution.Width, maxResolution.Height, quality); } } diff --git a/Source/Bookgen.Lib/ImageService/ImageConverter.cs b/Source/Bookgen.Lib/ImageService/ImageConverter.cs index e63500e4..77c19c09 100644 --- a/Source/Bookgen.Lib/ImageService/ImageConverter.cs +++ b/Source/Bookgen.Lib/ImageService/ImageConverter.cs @@ -11,19 +11,20 @@ namespace Bookgen.Lib.ImageService; public static class ImageConverter { - public static void Encode(string source, string output, ImageType imageType, int width, int height, int quality) + public static void Encode(string source, string output, ImageType imageType, int maxWidth, int maxHeight, int quality) { using FileStream srcStream = File.OpenRead(source); using FileStream destStream = File.Create(output); - if (Path.GetExtension("soruce").Equals(".svg", StringComparison.OrdinalIgnoreCase)) + if (Path.GetExtension(source).Equals(".svg", StringComparison.OrdinalIgnoreCase)) { - SKData img = ImageUtils.RenderSvg(srcStream, width, height, GetRecodeOption(imageType)); + SKData img = ImageUtils.RenderSvg(srcStream, maxWidth, maxHeight, GetRecodeOption(imageType)); img.SaveTo(destStream); + return; } using SKBitmap loaded = SKBitmap.Decode(srcStream); - using SKBitmap result = ImageUtils.ResizeIfBigger(loaded, width, height); + using SKBitmap result = ImageUtils.ResizeIfBigger(loaded, maxWidth, maxHeight); result.Encode(destStream, imageType switch { @@ -32,8 +33,6 @@ public static void Encode(string source, string output, ImageType imageType, int ImageType.Webp => SKEncodedImageFormat.Webp, _ => throw new NotSupportedException($"Image type {imageType} is not supported for encoding.") }, quality); - - } private static SvgRecodeOption GetRecodeOption(ImageType imageType) From 8b693eb4a63576e2408ffbb1861d79beb0dfd9b3 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Wed, 4 Mar 2026 17:48:08 +0100 Subject: [PATCH 38/41] Css update: info, warning and error boxes --- Assets/BundledAssets.Source/bookgen.css | 52 +++++++++++++++++++- Assets/BundledAssets.Source/bookgen.epub.css | 44 ++++++++++++++++- Assets/BundledAssets.Source/test.html | 4 ++ Assets/BundledAssets/Md2Html.html | 2 +- Assets/BundledAssets/Print.html | 2 +- Assets/BundledAssets/Static.html | 2 +- Assets/BundledAssets/bookgen.epub.min.css | 2 +- 7 files changed, 102 insertions(+), 6 deletions(-) diff --git a/Assets/BundledAssets.Source/bookgen.css b/Assets/BundledAssets.Source/bookgen.css index 05f47d9e..83284059 100644 --- a/Assets/BundledAssets.Source/bookgen.css +++ b/Assets/BundledAssets.Source/bookgen.css @@ -1,6 +1,6 @@ /* BookGen Css Not minified -Version: 2025-05-17 +Version: 2026-03-04 */ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&family=Nunito:wght@700&family=Open+Sans&display=swap'); @@ -12,6 +12,10 @@ Version: 2025-05-17 --blue: #003e8a; --brightBlue: #2e6cba; --selection: #CACACA; + --box-info: #d1e7dd; + --box-waring: #fff3cd; + --box-error: #f8d7da; + --box-foreground: #3e3e3e; --max-width: 1200px; } @@ -22,6 +26,10 @@ body.dark-mode { --blue: #82aaff; --brightBlue: #d6acff; --selection: #5D5D5D; + --box-info: #B9E5D1; + --box-waring: #FFECB5; + --box-error: #F7BEC4; + --box-foreground: #212121; } h1, @@ -228,6 +236,48 @@ dialog #container #dialogBase { color: var(--foreground); } +/* Boxes */ + +.info { + background-color: var(--box-info); + color: var(--box-foreground); + margin: 1rem; + padding: 1rem; + border-radius: 0.5rem; +} + +.info::before { + content: "ℹ️"; + display: block; +} + +.warning { + background-color: var(--box-waring); + color: var(--box-foreground); + margin: 1rem; + padding: 1rem; + border-radius: 0.5rem; +} + +.warning::before { + content: "⚠️"; + display: block; +} + + +.error { + background-color: var(--box-error); + color: var(--box-foreground); + margin: 1rem; + padding: 1rem; + border-radius: 0.5rem; +} + +.error::before { + content: "🛑"; + display: block; +} + @media print { body { diff --git a/Assets/BundledAssets.Source/bookgen.epub.css b/Assets/BundledAssets.Source/bookgen.epub.css index 87a42d9c..124841fa 100644 --- a/Assets/BundledAssets.Source/bookgen.epub.css +++ b/Assets/BundledAssets.Source/bookgen.epub.css @@ -1,6 +1,6 @@ /* BookGen Css for epub files Not minified -Version: 2025-07-15 +Version: 2026-03-04 */ :root { @@ -193,6 +193,48 @@ figcaption { font-style: italic; } +/* Boxes */ + +.info { + background-color: var(--box-info); + color: var(--box-foreground); + margin: 1rem; + padding: 1rem; + border-radius: 0.5rem; +} + +.info::before { + content: "ℹ️"; + display: block; +} + +.warning { + background-color: var(--box-waring); + color: var(--box-foreground); + margin: 1rem; + padding: 1rem; + border-radius: 0.5rem; +} + +.warning::before { + content: "⚠️"; + display: block; +} + + +.error { + background-color: var(--box-error); + color: var(--box-foreground); + margin: 1rem; + padding: 1rem; + border-radius: 0.5rem; +} + +.error::before { + content: "🛑"; + display: block; +} + /* PrismJS 1.30.0 https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig */ code[class*=language-], diff --git a/Assets/BundledAssets.Source/test.html b/Assets/BundledAssets.Source/test.html index d621e548..c018ad35 100644 --- a/Assets/BundledAssets.Source/test.html +++ b/Assets/BundledAssets.Source/test.html @@ -122,6 +122,10 @@
    H6
    +
    Info box
    +
    Warning box
    +
    Error box
    +
    diff --git a/Assets/BundledAssets/Md2Html.html b/Assets/BundledAssets/Md2Html.html index b78ec0a5..a5523a41 100644 --- a/Assets/BundledAssets/Md2Html.html +++ b/Assets/BundledAssets/Md2Html.html @@ -7,7 +7,7 @@ {{Title}} diff --git a/Assets/BundledAssets/Print.html b/Assets/BundledAssets/Print.html index c42dd2b5..427eeda0 100644 --- a/Assets/BundledAssets/Print.html +++ b/Assets/BundledAssets/Print.html @@ -7,7 +7,7 @@ {{Title}} diff --git a/Assets/BundledAssets/Static.html b/Assets/BundledAssets/Static.html index 4f679a25..db4d6bca 100644 --- a/Assets/BundledAssets/Static.html +++ b/Assets/BundledAssets/Static.html @@ -8,7 +8,7 @@ {{Title}} diff --git a/Assets/BundledAssets/bookgen.epub.min.css b/Assets/BundledAssets/bookgen.epub.min.css index 21e26696..9f2971af 100644 --- a/Assets/BundledAssets/bookgen.epub.min.css +++ b/Assets/BundledAssets/bookgen.epub.min.css @@ -1 +1 @@ -:root{--red:#970b16;--blue:#003e8a;--brightBlue:#2e6cba;--max-width:1200px}@font-face{font-family:"JetBrains Mono";src:url(JetBrainsMono-Regular.ttf);font-weight:400;font-style:normal}@font-face{font-family:Nunito;src:url(Nunito-Bold.ttf);font-optical-sizing:auto;font-weight:700;font-style:normal}@font-face{font-family:"Open Sans";src:url(OpenSans-Regular.ttf);font-optical-sizing:auto;font-weight:400;font-style:normal;font-variation-settings:"wdth" 100}body{font-family:"Open Sans",sans-serif;font-size:1.1em;line-height:1.6;margin:0;padding:1em;color:#1a1a1a;background:0 0;widows:2;orphans:2}p{margin:1em 0;text-align:justify;text-indent:1.5em}h1,h2,h3,h4,h5,h6{font-family:Nunito,sans-serif;font-weight:700;color:#000;margin:1.5em 0 .5em;page-break-after:avoid;page-break-inside:avoid}h1{font-size:1.8em}h2{font-size:1.5em}h3{font-size:1.3em}h4{font-size:1.2em}h5{font-size:1.1em}h6{font-size:1em}blockquote{margin:1em 2em;font-style:italic;color:#555}em{font-style:italic}strong{font-weight:700}a{color:#0645ad;text-decoration:underline}ol,ul{margin:1em 0 1em 2em;padding:0}li{margin-bottom:.5em}img{max-width:100%;height:auto;display:block;margin:1em auto}p code{color:var(--red);font-style:italic}pre{transition:box-shadow .3s ease,transform .3s ease;font-family:"JetBrains Mono",monospace;font-optical-sizing:auto;font-weight:400;font-style:normal;font-variant-ligatures:contextual;font-feature-settings:"liga","calt"}pre code{font-family:"JetBrains Mono",monospace;font-optical-sizing:auto;font-weight:400;font-style:normal;font-variant-ligatures:contextual;font-feature-settings:"liga","calt"}hr{border:0;height:1px;background:#ccc;margin:2em 0}blockquote,h1,h2,h3,img,pre,table{page-break-inside:avoid}table{width:100%;border-collapse:collapse;margin:1em 0}td,th{border:1px solid #ccc;padding:.5em;text-align:left}figure{margin:0}figcaption{text-align:center;font-style:italic}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} \ No newline at end of file +:root{--red:#970b16;--blue:#003e8a;--brightBlue:#2e6cba;--max-width:1200px}@font-face{font-family:"JetBrains Mono";src:url(JetBrainsMono-Regular.ttf);font-weight:400;font-style:normal}@font-face{font-family:Nunito;src:url(Nunito-Bold.ttf);font-optical-sizing:auto;font-weight:700;font-style:normal}@font-face{font-family:"Open Sans";src:url(OpenSans-Regular.ttf);font-optical-sizing:auto;font-weight:400;font-style:normal;font-variation-settings:"wdth" 100}body{font-family:"Open Sans",sans-serif;font-size:1.1em;line-height:1.6;margin:0;padding:1em;color:#1a1a1a;background:0 0;widows:2;orphans:2}p{margin:1em 0;text-align:justify;text-indent:1.5em}h1,h2,h3,h4,h5,h6{font-family:Nunito,sans-serif;font-weight:700;color:#000;margin:1.5em 0 .5em;page-break-after:avoid;page-break-inside:avoid}h1{font-size:1.8em}h2{font-size:1.5em}h3{font-size:1.3em}h4{font-size:1.2em}h5{font-size:1.1em}h6{font-size:1em}blockquote{margin:1em 2em;font-style:italic;color:#555}em{font-style:italic}strong{font-weight:700}a{color:#0645ad;text-decoration:underline}ol,ul{margin:1em 0 1em 2em;padding:0}li{margin-bottom:.5em}img{max-width:100%;height:auto;display:block;margin:1em auto}p code{color:var(--red);font-style:italic}pre{transition:box-shadow .3s ease,transform .3s ease;font-family:"JetBrains Mono",monospace;font-optical-sizing:auto;font-weight:400;font-style:normal;font-variant-ligatures:contextual;font-feature-settings:"liga","calt"}pre code{font-family:"JetBrains Mono",monospace;font-optical-sizing:auto;font-weight:400;font-style:normal;font-variant-ligatures:contextual;font-feature-settings:"liga","calt"}hr{border:0;height:1px;background:#ccc;margin:2em 0}blockquote,h1,h2,h3,img,pre,table{page-break-inside:avoid}table{width:100%;border-collapse:collapse;margin:1em 0}td,th{border:1px solid #ccc;padding:.5em;text-align:left}figure{margin:0}figcaption{text-align:center;font-style:italic}.info{background-color:var(--box-info);color:var(--box-foreground);margin:1rem;padding:1rem;border-radius:.5rem}.info::before{content:"ℹ️";display:block}.warning{background-color:var(--box-waring);color:var(--box-foreground);margin:1rem;padding:1rem;border-radius:.5rem}.warning::before{content:"⚠️";display:block}.error{background-color:var(--box-error);color:var(--box-foreground);margin:1rem;padding:1rem;border-radius:.5rem}.error::before{content:"🛑";display:block}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} \ No newline at end of file From 78a5f21c11103ee97fecea51d142d271c7ae7d99 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Wed, 4 Mar 2026 18:15:15 +0100 Subject: [PATCH 39/41] Publish script changes --- BookGen.slnx | 5 ++-- Changelog.md | 4 +++ LICENCE | 2 +- PublishFiles/install.cmd | 8 ++++++ PublishFiles/mkisofs.exe | Bin 0 -> 402432 bytes PublishFiles/start_bookgen_shell.cmd | 8 ++++++ .../Pipeline/Wordpress/CreateWpPages.cs | 5 ++-- publish.ps1 | 26 ++++++++++++------ 8 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 PublishFiles/install.cmd create mode 100644 PublishFiles/mkisofs.exe create mode 100644 PublishFiles/start_bookgen_shell.cmd diff --git a/BookGen.slnx b/BookGen.slnx index e45768c8..1ac763dc 100644 --- a/BookGen.slnx +++ b/BookGen.slnx @@ -11,9 +11,10 @@ - - + + + diff --git a/Changelog.md b/Changelog.md index 2f625c86..ab6225a4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,7 @@ +# 2026. 03. 04 (Prerelease) + +* + # 2025. 11. 16 (Prerelease) * Breaking: Template engine reworked diff --git a/LICENCE b/LICENCE index c68cf738..e8971bb6 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2023 Ruzsinszki Gábor +Copyright (c) 2019-2026 Ruzsinszki Gábor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including diff --git a/PublishFiles/install.cmd b/PublishFiles/install.cmd new file mode 100644 index 00000000..3aa0c055 --- /dev/null +++ b/PublishFiles/install.cmd @@ -0,0 +1,8 @@ +@echo off +title "Bookgen Install script" + +bin\bookgen.exe install + +echo "Bookgen install complete" +pause +exit diff --git a/PublishFiles/mkisofs.exe b/PublishFiles/mkisofs.exe new file mode 100644 index 0000000000000000000000000000000000000000..e9787c721ecef8aa9dcfe78072963bb15cf150fe GIT binary patch literal 402432 zcmeFad3;nw);HdrUU6-qf6COtYp$Dh*$9`bTTv2q zb)~~GYjKXl(W0a)+vOOUk(-f`@5lyK`56w!pA^UL-pI})h?jq47+Iv7{}cxz{Fm2V zB<`v1BHHC}6pCb7Rsb9pPokBnpd;7uYoAmEU(mJSgbwllCYa~w?kN5pn2HZ_N#wu% zH$uN`fsR~nSG0)jWZP4XOO`5hRL>YO>k|DE#)sm6ApnNq+r2ZO*DQtyZb5-uWm(M7_2`a5u!&YcSkjvWsmuM}{< z^Dpd}(JoD`|b>Cz<3{SK?%i#=l*P^aXUuQb(2K*V-faVrA`Ofp5>pRDH_BrRBJ5{Uu{(hhz z)nIt0v*y(U5YwV(7Zq#fM|=OF=V<1pg!|lexsJ;;D{Dw8VcRroTDE3+T96v=u9a`m zk6vv<5hp&<%w4-Q^@W07q^G_i^yR4Saph&wZz*jna~+8dq3){(B5E&%f{34FDLaq~ zz=7zp3qM7nsOwaGV#+$Ci*{d5z-}=m_n0ms!BU=;hzd}on=G>TlU#>=Dga~3?Z_nC z>bHk-(A*`;5+r~%h0q_MNbg=bs$mTQMr};A*spz?$tu78BGZv@A7}!ksmyx>$aaIt z$oSKtM8duXMeV}xn4p+D6Nyoq0hRrCfi?sdXqIT}fcU9Cv&CoZa)u8K@~QroxUz;V z&=L)|Wrq(GF8VyOQO~L09BPecGA%EBuy9dUWOHZ_(7o_fF1l>n!{`CMUz@*ZmUC4% zl+_h=rN3ylsq}O~xMvROQP9OPYG>uBgfAK_a%wX-YK@;~+Ik|>5wynqd~_E_z+4|6 z8L%pT@Gsp@Y1#9fX1=NIdrvdB+EoCgA;u{NCqkqs6>-RqHEB}7cTca8(>%R(` z!=EP-HLxxa^REX1=&7e$QOt5bj12HG2qsys#f+GFK}%UGlHE7rW0&`Lpb>QlVsOpJ zC)Cw!v_Jig(l~{wA;sQ;n5CSLm{zxrl6S#H7hmX`?z_-;k?&$L5MxTqUy^-48CBb7 zpbwTU6N1Ze8dF|CwxDbnvqyHwHaDm%a=_(@^_y!0>T4VQ(EvK+K+gOL#&^zO^z1z6 z8WxQA3YNF%qdNi|uNe)_XrH^HXP?=kuE>sT41FB$ErGksk0L%kGq1^ToeT0aRigvbmTsdM#0B#h#zcn0E>?o@owQHVoIQ}Hht#{`&)Z)QBZ zJ-(K4%)Bx`ru=|7$xFq_Z^nHVdZr4IfYsBmou=YLSc{&P4G|oHps=8tV#?+uJUV;{ zL=Gcu8MLcrR+nN>EEU0}%hLGSEdU82eRTr=?03(~lvRsfn~MBGilOB%st%V84^0a% za8`%T3NOfTgo3MB!tl&QjSl^Mps4{Gh>Zmth_Nz>^d_0`a5~`%CYS~9psk{v@uOo( zPolv;=!;~?81$~l1u2CW2mwpeaRKNEhRzXmOi9wQot)G!M9p_dIx=Oo&!?;1bEY6H zmQMH_2{Gl)0UcOQQWhusAf|+g6aS2Eg(*^sn|_xo+F@L&64a0@t0XlcS^2hf<+EhM z?sURbCYS}q-Ll%b@(XC=znJoKl1ihS6imq#F%hs6&9^o4y`UL0Jlk1BSA*tS^!R7M zZl3|f&6wP@`t5p7v%esL=_rm-HO1*ySM>8QJ;zZQ*{q+(?6U%9+!sDLJv12E8S08j zPT$hZNP~V#g-6e;n67vCEuG->HD^w*ieBKXicW9_qSres)fGJ}mrivOOHZQKbF0jz zp!sgV+#P?x@B}19n)w|l`U5F?rJ7B(DP+_lqH_(;E%~54R1{OLM}R_ac&3@ zk~7HBE#N_jwf&L?UJKw<-zUW|U~T|NcAB7v0V35LW@U7S`Hp54eEljW&G)pZHUp;3 zA)rGMg>A1vZ=^H6k{N@UQI*aB=pd$yOQ!(GY6(hX$sUx>0O-j~$wLabdoIHlfk6mg z&hS|>d^f`rW%zFlpCZF=GCV?te`I)&3?DTP;l46_9>d*a*kJfKv={jQ$?zT-ev{#E zWH`a_HW@yt0^xULcoD;7C(6Fe@Cq6Jh~cMYIM;*lUl2BT+ZXQ_Z2i92{x5_~<-We? zU)OT{ossI0VktSwK=h^4P#pZ&hpuvRzS{-BnD^+kGEHl=QbYN%9{`1|EQB-rHtY)C7rYO~RR6}7iltHY;P4YV{j+C0yV2a6~o8+Dw_E->8-B(v1CCdJp`~ z&@sF2wsU?>B!W?Yk$(+!3R9}~VaWh-F=JZEKFvr7@I(@P8$<_b8KGW~Ccm-Yzo@6t zFbj(pe>}VH>LUNuU9n_Xk!kEWR54s#-k`2%(40;2OEhzz5JfH4NJ2Z9f11(i)Qkg} z^HtWp5a?e99gg^j&P1(hlSCyOAV@z0!0mPRj%-#Ve}r+Oz6PD=x`^Zb%AW-*)cTP~ zCql2Vv1xT)E)b%LTiSVX=wKp&W_f9X@UuU6o~Y<+q^m2sCL7hHuGp?Qe^FP68V9Cp z{2nUI-v3jw##hlXtg#|p#!j{41dkE)ps(D++#%W_P#x`+Mc z8*4=q2BD(x_`+0|#Lyhg{3X($MlJ%O>T4}v*-$p%}m(aR64*%|&JXWHm1%8Xk8_sv58g^Q#&;2wbv`7#A(??M*W2{$w4a(@bjl=}0Ek z$T~)klXaZOI>x8#=+KnA)78viHCM}OLSxvB(sVPf5mj_VR1DX%D~77|Z;Hmx6!@nJ z{6X_```x{Q0l|_T?M%K52ua1TG!=gbRKQ%Y1oN;$jRyvn zykVYQG|gCBY8;#;M@I~0g65auaSn-NCE}~mHi!LX0R4;=VQIp>ZUs#c==A-GP`-T- z8CdjU$nSyNtsINaT2u*G^Fjv(!F01S8=q-rW%tx{FhefKnuG~b25o65IX$U_qwafH zdLs}Dy#c*9ABhnpQlGql^nW>#cqtO+CKDfGVg?ecka%e_aS0QDL}FQonUg4v*?q*U zhPH{x^8}P#O?_tHMCvW~`6?#_VJF^{?a;fGpFd1j?2(r_9d_j`&PDDrKp3YNJI(Wl zrP@|@!9`8y3DYWQzG--#$fJeZE2cca0R9Ed4Xp;ghGzr{JM^OiGdEy5>B727TC~Rf znOb?{Eq&kyc_V(2uYAKTInV*8TUl%4)f%L7wnLu~1UqL#v&^98yKt8nefo)-`PpGg zdEX|XjsG(!Vpc_>GuKMkW9FIsa>MG_()iwmoU0^?Bl7%ONg|JVHWqN5keE`zq zy#QL3PU?yzVUmJ#u+CwaV^zJ@*grj_YEhpv4F9ZthGsU#kM&1S+wKeRcg`DTe0M_l zV8*;+WB+)4u(5xzKFHW#s}CW(y5dYHa2(AND>ZWiroQxlb)eV5_}=@whFv#bT#erry5zqKIGn$ySf9u1}qgZ@sqUlcIj zEKAF_s+r?mdXf*q;9t5B|L10SmoCioF1;$tzw|1_U$VaB-I5QXXor3Hy#>T^4-#@T~gx^TBC!5>aYeZNH$;t8sImZ{SejYD@BZH z=7EHJpdhgf4ao9av$HL&JLdKg(}T>*I%X z%$AM5dd*dDTzr+eYafX0+xXkSl05+OQjj1`dQqw0yt=5EGYP6FgbJGOkDl(Vj7ExR z+dxoOj-u>swa@dz9e3!V&?H~UZ~mcIqn@F&P=~*0#!!FJ)o=p(RsW6&(X3g%C_I2* zkk8pHIOJ#De&-(W$cOs=3L_e zgY2!?&OlLhC~Wy#D`Vp`YPD$nB3;bMb?@vkSu%C1QdqY-w*l5{A?+s z@1q}C?^;#=Rj7YT;xP{_$PqI|sAp@0I4$?(GC{AZg69C!&bQ#lkybMlbkvs!M9)cR zL?}O8>l~@pUjSFR9M~DKee~{PNQcLb)O*JB!wa1w^{>@|VQ|P1&*30v z&OSlxHqv`C=`H{ChUZYTo?iuP#i?1#i=Id8g2*0)3N>?m&>Hd@8fthl@fUs^&b3fD zt@gMv!0Sp!FF+J?ae!{-u{A>+yJ|cRZ|IcYWr+EP;EA!*87#Nw_U*uvZq5F_NuC&< zN3&&(!sNJYB#eeWU#(i8jQRVp@Q(jjFF*!YP~cO{XPgw z{BE&cqwufAHU*akX!0=3x|NZ(P@!XR5P9N8=Jd=l^eRU zVFRi#$h=h;FyN-szjvYAXd(nZ|HUSZ+^)%KR$}v11QGI()UVl=jp8O-CkWx$;^p6#c0NZY-;5100k0zIFK9*B(3)|0j9<=N&dImXK}x!+`b`4i%!Et(GbFH z7KeXMkaLV6L(n8*7`OU+v!e! z8{1*B)7AQuFgLcU_!GorShtRPrd?+x?Jer``i;_RRQ*7{)$S!w(AW|iYLiIQ*i$lD zx7Zfoz^oN8yXbI$xg>pfKxJQ>Dhr>W1dh4}5W&yoq$^dRG^k7|LLyvuy4=zG9u<|{=z#P^`t45#@`UhwHR=c1#a@P8hbviQ7^1KD4X z#UWhi>}t(|C;y?MFoeUmihFk`@b(_kv(^3p!!_xo*lMr(nk$IVAT4^kfCk(@f!KZu z=`>)x;E7uQ>hC5pV`?jnRolvqiP~I|sK#JKlu`K6EcFl=EVSBy;)?_z`{;`ij%3+<; zi*3p!WV&&o`oUJ$|(SZqHo`3@D%fN&NkWXm$=4pqdDOSs#f zkgcgS4+Nv>23=f&28Af6Fgm~~OjQj)j}7D;{U?;we+(b==qD{pxK||0b7m^mO7?Pb z(HU1Mag`7kb&(cb07PDAvh}dv$&s$9RySv&nO(6E>0Qw+R21(TKG-c(NONU>Gs;@7 zmw%u{7QDny{l-wQ_ylAniNnTrOME+~K|6}QbO_8qN2r%(T?H@M0sB*U$uzT)rw5Ft zJk82w@%J=ioFfD-?0Q^sLCmk6$$zvMiFDQzFc1&635? z4Ey8H1S53Sy1FPhRN@Vdi;uuiz7QKQ0khEu8A=z(1MzjogDY2qD@ixY*Kn7%7dD6Y zzYTOhBLjAsK;$>~zD65Gi#{Vq z{>-K%wx2Er5!THa*;twDuJHKbK6UvzY%hM5UKhYdf)plpf$gt|!j91COa6p*tSgTL;Vr$O4H;`U+} z3!;J<*`kfMfx1NlE7AAyuI4U`k9dZ;%h+E~J38Us$>Tk;!M%?I5%WQWU|hUk!rdY= zt7oh(L>>0TPci2=JB{B8YQIYJuZZZ)riA-O0w@uv`|Te`sQU)vx@N3>U)BbR)v>ZG@NN-KRp7x2Htx|67q`ZYBUkh?TlWPLsw zVJb&2$#vAdQuGv3TVa=kx?@Suf{6Xbh|E+5Cp&CcN8PQE6*+i5EJbNxZ4lU$cfd`= zo=417b^|3MB*Rp~I8%fOElpFoPDVH{naV{xZDhF~L{YkBZqy9VaZAxDq3aBf6YX?_ zYT%iC1;`*ndM*!D;249`5kjZA=TmAo{gA=Ab_C+rAP!__0GYi;Cig?~g);eMCjV6= zi*}rzZbuKa!%_wwBwK5<$0-UzAzGHFr9r*{h-|d>ImVLb=lzPq*?;rC&BhlQhUZHW zYuMLf?8r1cn-Ie(w?=iv#+)~DT1@3tB;XWT+W~wqP=_6674F*vw^Jfu>>nb0z&{R7 z@ps-xGRJc*`h?|1{(iZ-{b5L+DEd>n=r~!_`kz(#Yr0$}${C)2p_n6da!fJU>-cAQ z)-g$kSHs;e&6G_5j#DIp>7>_0`6(-4*PwPGwQw`56Sh!{S)V~NdYItr$A&1Vhh2*XMY*2{nIzx)>fS^&Qni9O?KwJ=}x(JI6Gx5 z7=s;vm@)_jg~e^T?nV$|XXeT!s$kI2szo$DiS>=Q(NUQwT98OO7N!vkV0ZXcAA`CCzu-%`e6Es0IbOg*1} z7iDt;^A4W*iiBrh$#oLB6=5<8A>J-45hnF--C4WE1ll* zv&jBHe%_Fu*ZC3BT$3i|=b>c$81y#CG?bUx@m-F3Ak;4lL1@r>7|MOjg#3E(H9W7@ zvCI&|b2Tc@T!d3n(-9aVsJl#7cr8CZW1Z9Rj7QL+=NX<;39bHxyWH@P8=lis8ACc{ zK*?>oRrdV3oqOVEB0a3HpTqLnR7L~Cf9mT$9h>Tc#0flQIsACkP*uNEpT8Q!VSsv; z10rbtu=h#zzP0LI4P|RX-DAq5q!6-=tsy960nutlP$P6QvTsFpHnT?|dwMeaGMRl$ zOqq%7Qrs(`*Dva>Op|akNPrUf>L;lL{(H@LLb|9=7Jc-8T=e*K(Jm+|Y!B#zMPjfS z%3HwW^sa#G(bFYNmM8St|4t86_~!NVt>zca>5@kLY)NWeX}KDJ*&h4>+J^x^<*BB@ z76UA%oQy>x7n1Qp^#5LP7aHs5qlC?IfFISr7GicI(151X#3J?g<`lXxu{l~OCWem+ zQ=K~qbwEw7Mq7kdd;~Q}t@sXLfcO#QU;uuOlfu5JH-UY5fuS*tVMj3sg}pl|vOh8q zg2Md{0m$-~BBL7(HRU}fL0J8Qa~pBmu#>*jSht~EAwOgJX%}5^z+vxwkD}{B4($x7 zDLbf*sx(oP1yOg3UOUzBv|+K6xoC``91uh)-S&u@K9aGYc)rEL(kV{={VAQZ`#2g$Zy*OH`;SxB zAtXQ^PJ_Gy5W_RERt_0<{9PcDLuUIea>xwtJY;-4%K|Yxa8N%UWZaU*6al6<7O@H` zgD^gb^1fFo3P3h?p+q*+XRLR2Mkr7n3shI8QI!)FXZs}*)&83}V3Y%?B_pREOr|d& zE{XAkeq^^J?axT#g3IunFOtZrKpOFOAjSx~I0Zc!fh2~LK(4^hwLNX$4@s;irP3_d zTS()y-Q9leJ1$?8myuw2iUrD{hNm9`YQ$!{p-0kHUp9n{c8c8kHw1;>6TMnb9-(zd zfpQ+mn`e0b%8bx8hUYaAm}PiO5x4-f%e*rYsuO9Ggo?KxJ;h12U2b@863L|im@Pub z8JSx*u!)Z6g%$2w1N(ZvA%rSPL`x}hKYcF>8=i$CK#)_@ARYm-l{wr{*&sl`y;TUXcMMN`D(QLzgmL8g3zO8y10vrA zzB%*-hUaXihRPvZuVZOT*&2>!o-R@cqte|4DRwo9N~bYLKhN;I32=u#*6{QYu*2Jw z90X?QMJV)By3idc6jSUt3Sq>d??v%^D65Z=CRX{JKY+{5`;!v@zY-LS2+v6JD1@#% zK?KNvPts^B2C?h1M2=hBF0$91OyTCrU?=LpL^YO}LhjX+G5g7M?m#3v>)W_ac}f5( zf0m!S}*fy9BeJ=YABRc~WX zy~z1ZaBMjAhNv3+clCj!*q0+0;cA92kl~vdo+3)UF9@yyz{5;DMFM=u@CX?m2(@Gn zlHm&&?kfsDC<>ywy99q(U5L5+j}_IeX83y<{+Zz&3>%(o0Zo0~Q`#Z>fd^f`8y~w* zm`8w=%UY3Scs^haI2VH6RB$CJb&?3{Y*}Oli+sLF6gh!KEcZ};u*NzXg+db{%y$vA zz5zbn7kkmBs@xLq3KyS&6J z1^EvB1Q4Q?XXw~IaxX>!3HE#;+w6Weo$&ZTBv@m74m0#tY2sjwv*?dkXcgu}-LPT13sXLt&*K6TbsB-|H9C9&v%{cH++g7gF4k_^)i zs3QzN;NFh}H9xR|Ms~E}d9M`ne`ti^Sxa_>yh!qb40+w>qcqunL6yjQg3vl=jiU%8 zJWk)GfpoUb@Hj+ysJUG5QZANzfNa_RYSDhBicN(OoFz0J#OdK4q`a9D)u`UJwrrC= z41C+6GPTkEa06ZA;%vZiV3f+zQCBvZG5KhEbQ13K;Hw7++{og%EP7M7f(i$oVUy79t^v&2YTrW;Ys_=ZvI=ssqarWx$b*);x=;0 za?M3iyM}0z)m$p8xrSw*>`&HT^*%3451PukQ+)U)bHnFl@>WmhTOHk#NrhsN9KAesgi8$dPX(!-eR|n-E4< z9>A&;v<@aQF^U?T0yrIdz}~x+zTrQz<8)8!b3NE+3H>Cog$FMO_D=PDCHRn*i6GnB6dMNkIX?R)ZdQc9Ak2&1)GO9|3F3gHZDc_l?N{K5<|; z#xf3p&Z|b61l)Cit8ZI0GU2XjN8U`pk09fQ=jqSUIcojA05+6I5plNOhA^bP0Ps?< zCHB=U^cV`sL`ct_3)m9(24sZE(GV%Zycit&4sP$^RWFdF3J}nR(eZ{q) z5(-DLH(l#=h@0((Cr+Gd{awOT-?M|%VPbhnOb0yHN>S#y&@coih+~>wKXkr}SZ#rJKMoPrgMB2#M-sPU*At3y$W^(LsD%4Cb^~PNhRrNl z+CsO`i$I0@?jrn!2tIWG(13w9JgqcCDjm+W)DuBWtzL`_1Ub`A<0jv0AK*TPO9Klv0V{x4!v$z^h)7+(<( zEv+&?Pqq_>&C$(U-iIPx?-VBkI~gB1k}fLW zU(XIgQ|2~c9BIMoOb zVM_Do#1bM=BXvl@*`524AAk_#qe=RO!Dzu^VD`xiI?>?^5qX(QRn1zK4<>5nhd~IM zxoPi{KJ~r^^{z%=*#`X>Bx~l6(4HM^znr$Ph7w+wnwFSQU#amK!J^q-)>dpR6`Am{ z@Szwr(gh}-?KsKlX#KS-^zT;QSFoQdMuVE1#xGq$JME$&XpiNZ1bEvSa5yrR+qZK= zYcc+UbTZ|={1j_n>_vTflVCcmwMM|;E((fgv-p64PKQ&eLL&T!B8#P8PM6kE8bbXs zifdMNJ}x^J!#DpiaSR9GGXR86QJ}nR$7@1o^uHWZ2bs79>C2F9FX#ysdmSpAPkDq! zD+`wo4|Ne&I*4O7ftp?wg+H#5g}s=*G=fK6S1@}taSVlaACGMvC~~6#i!J3jKnty2 zD$1{Mgu$;y`*q;r5f;p+Vh&C@F%|*bej1sU@+(^K_XOB0-$H4$bDb?uIYZf|(u|yV zj=^i$FC&QY{BJ?$w}W1dAo#M4d;ycc5&;;=7c)R3`7DvbHP4yvKu90Gl}7a;GMu@) z1m~Ii-e%+)BKMxm?xpl?KX@KgwH3ycL*DvS^;o15-Zu34@aC#Um0g6y*q+&vL~g@#-|>U_$mrA^=u`U3OQ7;X6q~T*B1d zkSzE&(=>Ev;>Spg=cC}Kq~RUJ5Wb7ywFq0vgTR4zbMO~}yG;y>?P}yfhfp^2;H=l8 z!~f;Zf>jhE)So-R9{z~^KN)R#l6~H`2<@_z#}L+_T(T`!3~WdM@FHMz=$8QSRkHSN3Ah?z0tPJC zUj$%$yx}=XfWjBqO|TFthqj;=wf<3X0|+XRZ{IEvlp&m~iJixq-Y2k*6Djc+t1Lu` z(^=wtSz=;)iJ_u|eGgQsQ#l3^fWA&i8wpUu^ADC+BgZ9^ZNapUUzL zVwSrMppvw=ayiRwS^#@cA;E)SQ_@7J3D9+#u1399Z~ zgGlRp_ycd&GbeN2TrSRaVnuQ&nB{2w8iu8)bv8~Uw)*i$SYNFNarsuvW4J}Y)B5eV z2=I*2GWkzkd1e>0r3SZZz^KbVP#m~i6V`_!a;}Vc^<(4;5Bo?+afEQv<`f2e>OBoO zu-IUDPG(fc{WsS!EaNu=-QcH5Co0Lr?v)rNX4R#yqpjUOoj;229|^j4H9FpMy_8^*8vf0IndwF0z!@ zny|`%DIUPRN;t3pAUk_oH+J?Ck`_8OrYuCjDSFU7D-E*(FeqfC6P}O>kEIiqBVift zP?R+#AEhZ1SL{B7i4wx;+|yuToK^!Sjj0CZV)7Lsa zOt^2m5r=r2;A-lLdGI~<61)<-?XEaX$rIY0Zh-*&f)3y{?ciJRd{r8};~fxm1+PZl z4;EIL>nN5CO54f44%c$9kiy|rCT6O4@N7je>bVOd>wAr!2I!KVh~OgCgiIb-sBq3J zLYOzJa?ws)7Yr5^v%wTI99{K;^!%JmocF#e!y7)7Ie!G90Z@#~r-Z}$24&Zy_bvlo z+2!A6CvP^yuk?wlQ}7?rMG^m(*cSIo$E*2i(EPF8jjx}G6Y)FYh$HDGHr8Pbs4J#s zXwLU=s!6?b66lZjUWHViqcgX}yUVa5K47iZX|$zZ_b9}SIQ!yUN1J!HSFP_3>C-H4 zKCOG~0^rtXWCt)}Y5LVpSH!q+E?r8R|S?qz#<;GOQ~k9d~R_lJKmdL z-Ibh{_q5W}p&74<<(qyLLS+4$4G^X@Lm?O&N>?k zFw0&!k!y#=EPYs+{r0zz5SHm~O_jj631Fr6BG70qCK}Be%l6fe0psjX_k(S{&=azc zG%Q$Kry&D;E0lao4Xt7ft<_sl-p+iJBVj#V)#j(GnB;9+@6>yv@0V7BSGV8<%3SBF zk3>c+L+`1s>|0~@!*ziST$IQ#>kX1u&qU3-n;*Yr z2-ad@vG@gyQOn9_CT7X*cAbP436_N=S~C3#nJyCGq!%N!!;r>JWNI0Gq@~QqW1{5~ z&YqXccM~Abr(VU1C*wT%Mh@~l_6G7(+Ge=PFNS*hLB-^?W`Jt&v=-^Y)4DtWk+ZJy zS_`YKxo0OGt{U7Tg+B8DTw-**-rbcAk+mrHGwoG0ql)x3oskhc^<-0-`sL$D#~&76#yx z;lRCC%)8kL2UY(L95KYl4q9{b+a}D;QtMqAl)(vkR0Vysamu3eaRx|T9)tc)R62Rd z|8)!*haAr*#pHOlrecg^On=yEOlPFW^bP-eW7?X4mDH+L=wz7sSkgLH&&D6yY!*YC z>5!XZ&ALkl(Z5(n;UbQS3po}p;+VLQW1S9PhX&1hI{2>@;)~{KD0Di^0<9EZa9hJ< zemVqD^Wv-FtKnOXZ#BNt@SUbvXU%}mWCo1Ln@r)OmJX$qi4NrgCpD`Az>_vQ{ z^UQjoug$CSgJuZQ7JifF5}XYS8PEswK7Vo0oK;i^-yd%VN8MX1Ia|5DnSiR#z6HBg z#^l*q0rLllMTT1cGpLn)KAt{0jQRiX_xU~R{-5@_mv~67ZD|gq*cdN&x2F_BlbkoqCww(Zstc&^ygl=DR`b@uKG`gYYMwOMh$q zwOEr;Kb9rjpJPvB^-|>8zv9X&G+Gfrpf=Vp4byT?GeH+PUtEGo9r3u7Ojf_p`V^5X_(Ov{c<2y%eJkBoGOUINCvdm0#H3EC$>2^7p?iKy!dChIcU z8^?)C*NaTKv>kAq1YFGI$w@$vQY31u_JWSGnD=b9O%Ab-@u(U%dBK}4Afh90KKj4O zo4ql#ou;gr3G(XCX7@}ht}(RP*@i{SHM^!xo9!s96A?!t`oeO*#zN-#(3YClzD7`P z2ywTRzY2VG9N#L<$Ljp%(@HYvORkCC+K2oNqgXzka~Z@fmNI+78noO}u1ppL)G|OZ ztvWw;>v9ohOq78xeBr+^Fdi9)p{NEL#@!p#VZYfE4>oChPRldDIXNc=cz2^34|c!7x7~$56jpz8GAs+&XTdaWh^LTQ5l;kV@qVrD`Rysb_zaN zL1yBU@^6217W~^byao2fe6NdgV=w2NAz%J8e-Kl1Ta&Q1D;>ByjHl4FXy|`$W{w$! zWFI+*CGP}5n~)T~Co)<_$zU+68yeFth`*jmMoxpw7*b}imkpUR$_N~t3^8~b3qkvV zawjnVLr7Y5G6c#!nLOFGlKsADU9#VAVn9D8-I?h*a0^zaq-5GW-qFWK5Gk$k)y*pE zk^rMP)#9J6qZZ8G*~zA0=7woiFQqhApOR1m&(AmQy`l?ux_*u;S7mXMvyXAQ09V4`Y*itXLufO z06Wykljsv^tA6q_8PGrm(01*p{xAQ(ss4+m@YtCbQ=8IL2Ft3o0#;Qicz^=| z<>gp0tNy0S@ouKZniI^2(sPxDeDDo(o@xDC#vq>kKdEzpsMS zyI6$cXo66Vt#$%WhC(#u8v*pfS+0u!Wgq{GoM|kr%)aIkuJFZcMUUWqg{A!TY^s)R zAkXmZdkwXOj*=|~5?;jrt~GTUwJ(;?B++M82DuYEG59)8U^rTJlnt1nY_a^5&hbl- zq*=mUg4S(d>qgpbm8vX!Sv2Srv6X4NzepREcdnn1pxYX-WIgbr|R? zmAi<~XD(r9sjn+b5Jl{48Cxi0(-1?C*C7@#m&`y6ie)xF{D-M+j>x!Av?#V@K{`XZ zSe6LO5=)aAP(_36Z=Odd;ps;BfmMHV{4%;WX7JAif$~G4w$>{SQXK_er8=tj!5!E- z1%D{;8(Y7DN3nCSKx2aQQ0g}!tM$?&@mB5f0=9OR z6tP7Fj%Xgl*@XykI#4I^WsC3aMS0>IDayx}7z)LAc~P@)u)Z;C8%Os@7Nap_Ji#n&5!)Cyn-4L@*2{?cK>J`G)B2$*5$XQlE@U zE-JOjsAQ%xKN%%YQFacZt)KM7!k8J0ukw;})!fNC`8S<);sGzvu51^Rk^3eU~;?l|)$+#DBXoP~(lJQc+@jh(9 z*knA6_;HAzgg7@&dD$$Vq7ijH1)kd{K`S8pA%qj|OQ%DE;X1G47CLwM522L(O*`i= z?a7}TO7(a@_Dg7Iy2t&3a6YmJw@z3lte}AnW%P%1bvv{c`k+O zhx0n3I+v(IK$Z0Gq1mt(YQ4F5d4{dd*1IR&fUR{Mn*7({hc#KRNj10rh^)!UC+}PB zGgI_&)*yXeNgtZpBSqgCo#=a?kGy{HU8R+XYFo}&j_=xh$gGfCeV(AQ2#rBxqB zaldK21#aRT^z#^(xRN4u7tU7MfB8FRCz0zLCt!8A;ZX<{a>tYd|CFa&a46NY3t@+T zda7B&j-Y*Hs#!gb$m(O=O#;}A`}e0ROghV&CzLqa3_fS&)UyzAHc#k@R?&kd4JiBB z#}kR>2`4z3Ck#a!ej*)eWIYN9-NL7&FplUa;6DVPZx1#kgF-JD8xTvWSiLYjxg-ll zB^9nIvLxTK3o)ifb^|6WlM9T zIrOj|RwEjV87~WZ)%sgOvUI!DJpKQ`?GiKvFlN zIznSpMIuKOISO7}=_!jDDMy*_b?SilnQ1NzNsb_>uHOX*v?D$!Wf<0&0%LNyVhXf! zvVj!C19$tM;elH?*m;CqgMDpr{0-g;YX8R%P|rUC@!-FhIQhX4B>Mhj;^CH&em+x# zh1q`puTbxX39k7w1r8nNZ{qc2Jv;@04cX?zQ!e+T68K`#p;Tm;6qP=;OHA|B1{oy5VC@h zIAfJAKar`&G4<5{ru-m6{`wUl`*bKjfT`agHI-(6{RCMnHe3$xKeZkLla`5Zh8!Ui zM>9KVudF*Vn+)w}V6)4LaoZ1l-EL67jgd>=Pz!)lBcx_XLX{^)<%rRY+I$$=&yt5c zHTO6J))}Stu-hREv5BRKGr?;gB@?_%z1h9 z#IMYbpQ)99NS|j5Uh1~`a*G#+?$G$L{9uCu(akvDQvRVDNuaHOlWCz1nsusHUM5Z$ zzX@@Y>&q)Y;}!kg_8(Cd&M`kt3dX?xXhaB?@T`C3ci`z=c;Jrb5OFPNZApvzT7%y_ zXuOeM-{4pM2My0lPYLmTlZ{C)H(o_7Ie5j+3AxhFIPqS7IE3FQNsam%Y(p8IONdHu zAN42%GPl{=ej=k{SeRw-Cych*-(vxe*?88BLlPKC*?jj}h~hPyRGa0avFsfPDNEer|XW|!(i=(3N`C*AL{RXnWnj%Z$4CoI04m^i%hyJ*_^a~CkB z`DYq*wPI+|X=obNh^Vheirx@0>35A3y(MDP$RR+|iHHF-1JRf<8-E3Bks;?>V~)sJ z&>=(4utr#9EKO#}CEO}FNAUv>LeHyr;31Wig@_d=w0u}IAM}c1dC6j&4AwZXYO<9A zbR0VHg%3Hx)??WsO>j+H<;Xj@Xe2vaBCb6~uIkUgkX;=?y8Zko9Zk_Xu_fs!Wc&Wi z&RfL_G*fJ48uhH&Sp!P(BO0~C1Fty10B&wjL1ToE67^R)jIaO5 zV}@sxEYi&Ti56lt03sNK8j-DU{X2+BDU`X#9-(hCuwT;p7yJ=R7fxyaSR%b!YNQOm zgWTF5f6{#^J*HtbqT-BvYBAaR93rVnp!X3~)c=5Oi`J3Y7pIS?vp=YHKd?&;(dzsa zcMagNuUkj2bmzmr9d~WvwFY+r{K-&wt(!RGLTqO}!Es&B^Zt0-_MYSt!8*O;65+GK z(A2$f9TOYNQ)WBxxbK(9kDA<#e-U<2@vun3y*3R%1$6F>&~-Sc4d+2s z!u=%jaQRz5P5SEYAofePXTVKxJ91tVbd~i9bq&ufIpke|_xv?D$>r88-mbteo8AOQ z?`p+0Kdb;WfO~b?d{bt7Wi&e(&9>wU#at2wq0M*-+wm%8IvB;x&-$G=4loY=21nKd zcvb=XMtAa~PT7Jhr17HqowYD^P^?nS75I;FsJ2mS271x=aTj-qxC7wSGqO^5* zi6c}>uHur_KkjCQcX%n{?r-TH#xjoT1CN-YkbUjP@Tm+!Cf+gnEA?6)M`3xHxHDRx zgN)=ofMDt-NQY|xpsvF$0KB^o-ysBM9)1gJs~n{Hs6);yx!X8xy9cEc5?kQYTSa(u zDks-xAuc%CPB#M7F%H;>-&>SF?G?Vc`}mQ1HnyMrE(f0Qm!{Xk2)3^9b9Z?AeS#Cu zs-IUe0#_~+6k{w6+eo^Zc8DJ8!=B*;f7?Xw_Z ztzD1{o&OklgIj0rZ%5L|+iG`3-RLx+(IlEru!odzM>?UIMKrlY^Ez_nz%@Tbhn-i@ z=_Q_AU)v;}K74>s=rGhks1d&dg`=x;0R@dv$QGpxO}GFtvmCG&e+>gKEwE1`+;14bgtnIw%1F5f8 zSNdkxU^HvSjxIIE{w{i6hmBsl3>U5K8D|0s*2mRBYe7D)QLB+Jz*Np}oPDSYrZGZW zoRJy4?OuwZclXX;ae8EJG`~q*aR=!`Y{~7@a0j5o} z?~*XMyU$h&67rLXu~Y-d4EsLd#C5WGzcu4TeJnPa#XxRmGq9zSXV{}VBnz637)pEr z8an%7G>&%1*gL7bu@t|#XvPt+8elC?9zde!9r8IFeE7xY0{jxw%+1Y_KI}2Pd+0Ov zE4A4mpWDn}2KaCUp6DNV7i>N*I77%w@B^%dXWL&u0601oPn%$i77vZmL}GKvYe<3n zI^=MMJOCvDQP+Xp5xvT(&Dku8LSuS$KW{x-Q4Am&M3u0^9?5(zvcMVDLdqnJl+rlI~G(~ z;&2U`v;u=!szT*`tZNjJEd)?qxvCHNSK}*xonO_MjKA=MMKS7X1DdreAMt>-k{=d; zzhfGHnzBE4382k2Faq1YI!#XHKGY)EnO4D9BE?<;iTyW{3H z>tR}b*dx))wQ<4%43&D9&du}Tm&{9-;)rQ<@+g$Gi%?8%ZRmM8i6RP>8~o_F9(x%F zyc(fZ35;^YV%Oz1#5*DqBhubln+b`So7ajTUItFAglJS;jLyazdbbO{2CUa5LsuNe z*GAv(@O3;M4C&0*UVK7iB*f_7^OXZa`yJkHV^hf64;aw{@1J&d_ zoMt4isBM0}1+TIQe`cN@Olq?GQ=A@`;`E6yUC9Y`MP)}`uR`GzuQjW_Bd<5H6E!<0 zKxX~;CsJ6PLZ4*2-VeWWl0wJ()mH!=YoVrX(S)X@hOhUEqZW@nD7r2KrBx zX-)!ms>b@A@Owz1E?0g*jvCpH6l1~JFq1ZkJe;eo$4?}$L&O?B1N6uTgm65yXJ06l zRT$wI2L(<>#M>1C`BOp3-gt{#wDkLUOb3&H-9ux z^PBNXoWi+d2nd$DKR+$UEGe#o`NZi|oOJ8fOrjS!eb392&aU(sZeQy5SC95n-bl;x zCvSfV1&f2=zP5%=C&3Z#coZDb(xr2t8u z@uUuNgn1bQ=FYC;(OLII`C^wi{P|m={qU!}rqS1g*HKZie8bsMEQrQ2h+p@R3ogxk zm8cERZ^vQk!@HPbb(Y<6k>*;CEc+M}O_2%8)F=2kl;p7rSt#<;9 z16Fhyj_C=R(V}N2%och#)riSTJGl1Xv=etb@zx4n)WcgV8}$ld4~mR^;t`fq#$a<( zrmH_%hC~0MT%$4DbmbxzFEbi*OqYYf;j2IZ_O?73h=m3Q%~(4nA`1^1AL66ho93}tdas)?&&oI1AX~gb;V{aIwjx!aujs9*^F13P7Yd=Fdw+Pr_sCz zG)Tx#C#;nTlhO$*-U0;A0M8)1JN45ENJ(jnAzz9I6+K(=(aPiShk<(Blau2b3g$-< z@8D-;XMEf7;T!x&7hsYGIx-W<=Wn1j)X4M5GCZ%}Ls9F7?uDWLXC$SCffin1=Ffih zHM~!@Tg!}!iot3A4jrcM3Br2ChO$s7QTG$jXZC@2(KgJ?nXeN12-~VMdbJBowU}vt z0$X3!7Uml~Mlso83g0NaTa58vJQoqd^IxiOzDM8S#v7e;xC#pGUs?XP-ltOBml^>& zq}@{WCWw*zos%E)Ld^pMJ(}HEQ}mUh+nHdlPu~8;)lO?{>kgz@=TxgJCgz!&>OSYl zb~e@>;CG%{w^oewFF4Z8bE=({coU+L98@Duu-sx^N-baf2>!|^jWp|y`w_tzH`bLJ z_Xq9gaFPzjHvEiC&#b*NJZ?g$62Dnl85$4sqK|!Ct`lUy!uii6XqlB7p2$(mN-f@n zr!p%wtSvLJVL`n3*>M?AS4_t3>NkypnTzgCT^8nVQQ|jU<9_}bpEBuNhyR2<#iV`T zT80Rejo_SMcI)FjP@0line2tJ9h+{S$~>$Khugx7z38a0iD?sG$9xEBseL zD9(p`99Lcg)pUm#;!bVy9fi~<^@Fm+SBvX!UOsetRQy^?S-Tike>HT#Y6Q#)GmM32 zJ3^I~`$ZgVH>aL$O{nH%l94L6hlPi3r|n$6x-# z`!>vMG+loa>5nGUoefUq34~=SOSuPOqan{!mZcME)Tz2+H0G6{_=0qLT_aC@ny#93 z3U1lLz;a!P1ba3{Vu~5XP3MU|FVxI;g|mD;3ToEHatENt9B`Xo!$Oe4 z%lO6dV>n6owq8m8#wO*?}?pLrKro`>}ImUlWm zi>Dg`=3BV+t6Ak*^x{#Pbpmem;Il;9qYZkFR=!@1Sftge?~oW%BeyV-7V}nn>J7qY zGs(I$4~f=2{9C?By`AT)pLeQvK8xb??l1(!V&oaavq4TP&Ti|3ET@=@-DchF>fkq36ZV!w2`x^>fQwZurCei}>q|^7kDx;e@+TUC|eqSH-IY z?)bD-=-5R3O!+xoekRCIg#;NRKOu|zB~(lPUr`#dPe!C8;%iJ<{urf0=PEaI4WROHv*G*W+=lGm?YtJ`OPPf<@>r9S52{q5)Hp63WkY3~E-c!-oz0_713Q9KOH`NmgV9S;zd^j){sZpYO6OXrFMic0P-2TA z-fHhcMr)LuT!5c%8=~Q<7HpT!?H^u<-x{CGB1YV)mG9NN;dj78d*V=9O$;5I#1eI9 zi&w2%?I&4=LKLkb&<=-VEN9uqf#}B(-gIWlq22NmC;$<6LIX!HpTyn^&+J! zHJu;+ra>{zghv%eo&+B^wvu<`V68Smp;?4;YLGpDP+0ok!@>8 z!<*_+s3ZK-X1eyl6QVVKo@rkvk^G3P;7nx)k^_x9GSllQ6$PP+x7-dS&+JF_(Sm2N zP4s_gI}_-ritOR1lROBJ*g|ew>tx zRLXHVYT|i}n%%QBG=)c9LsLTVR{S#sbHb$?Z~C|bCgv{gWKar4d<$NyLmiCE+?)%$ z?wqnzIb}`aRBxjE6Qv#Yh>eID;`!R*hbGW}Y0QK&US7mTEuP1oP>24CQ0(noQ-Xbm zuWe)g?cK0sO?xL<*xPD2xi{Vl&zoq>w2Vn42-y=Sa6K){ID;6GDxaFcSs#IRUMbOL zR^H)6xI+j99zqh0*}OA{Ct^>`$0&lzyr)5D-oSn%VviDnWasU-RsPqBbD#4f!gg>9 z+ShG-&bcvB_6kWd;zL@DCOmWq5PYNC(nwo)Q-!)QHOiXtRj<_I^q`8lTE+C#i68s9 zSkkj}eLtr@rE=*8sRcXBI(SSv_;d+2E+IJVjLQsLfpK^%>h~;&&~KktIG6H(;2J%9 zLr$)|Iv>tr9S-!Sgm6Q4PyezBSwpaAx_=R3nufkTOa?hxx;g%Oh3_*;%cL~yQr^}r zLoWLfXBl7c!7EOsL&riX{C5mcyTrT*3EkXlK*<)P6u8|6!^pth96Vr|V zE810Q1|l`p)Kz}PW>_yW#`|T!E~aPF2^9~LC`)i?m&t1vcHSmQvfvO&ilkeSBx2|C z2xwdEF!%LkayA6l$CqTiU$WMmpW3L&deVuHsog#)2PCjwX2%y+9G$_pXAhfwL5Vce zbVSks=U|#nUEw$s3glzh9^;)`4JLI&9nfniJVqyFY_ESICdQvME~K$YGljWQxqO z#~Rs>?sxYh@E5S;m=Oc*SpDyj!olO7L%PjgQczZ)(%kC%j3Lu_2v9_w8!)G|%l-BD zT6bmzQJojP?q^7&WzqB3$>la|jn0wbK|1hsKfX;nQKFpL!M4ERCu?!7oClz1k#{qY zXd8QRe69mlq!&wjVqd9wDH^VW=|9q~5W9 zh-nnF<^;;pO&#%!Y>1VFI&3|zR%3+R3?7GfV-F7_(mqvcvTx0yv<{Y7O{YQjy|mBt zn@wiZJ`A&lcoSDG+MK}ZBF)lUgXD`nktln&4ixj(u_$LK!BM?@BgiYTm#B@AQhN1Q zDaTSlE$Sef=G3%KQfWKMHNb~IW6ieRdN1Nt(xxOE*ezWv z+kb-;TzXj%cR>{7{<thHCLcsmx|h-gHLmO`6RFX@`pS<)_=5L|Jqr3RKCm2#ZV_~)%c=b3!C7?JGgb9qOsY!1U_V?hz{I8EfDqq?TJZ6EkQ*zT7p3$cn@p& zEs99UH6=9wiptchFJ%$RHLmA61eaLmmxZjR_L14xEj2P%qPfmc?M(+<$hmdMYO@xU zB+4eU*UMNKNS?(-815f)mM8%WPK44NZ|cd}7N|jtuhk6pSDS+C7u?3_dq-h-WTZD9Ms|p09t03%=D3KBvms0TBa+E2O}oriyhW}cQmv~+((f;aLKvA^sZUk9x7D!vjZErwQpU> zNyaXB_C6s8L!TieY&{|KJ54FJQb&qPVCLH%TOb_0;ZB+g-6iWd+}8KV(S>+q0Jfe1 zZOhZdgA-_tij_#@JCPHQ5;&%!CfoMX7AVX`Q0~O@LZZ;lLxQRz*GQzjNDBIINR4d9 zJCK@1iq!l~X2Xh;r6A)nG*V>+$>p*!_ps09lZWoCp<^Z78hVa$wz#*c)Bvt0jG}3g zjb_s~y|Mxckg=nLh)0TN6J#qD9IG;jmB=D{$$B7`4s=h%Og9ydA(%y@aluIB5DTFw zu~IuQjC?7wS^I&~=rV>d(xxI5W@*z+y_C>8pLARzSe%mx8M|x6 zfb~+ge3=U>Y~v$7oq27x8re1;^VVo9;yAq@*S6%qvQgO}a5?iJd6*q|glvKx=kF1? zW%uQ;FWQeTFKDQ?U;)iy`C#Sh8x{W>_E)%7c448Y1J$1{+_ex#!I(Vv_RCO$_GGVn zjJ*p4-17k;9PZ)9BUJuoRtjZWmI1+8auunB)3N(GTWfuQA0#s}>IA)u=&KC@E@I6qlxfF4DYLO9}J{4DDQ4S5YUFp!QSIn2tJFU*w~=X z%z}w#j*mB`$q0~_0iFW6BqwVl1`^*-p z5;8W6dQZs}xT*eR3J~ZP*UPG+M70rnR+06TP@0ex&)-w8j?n#e%gBsiOXjG@XLv`p z5C*u6^}Ey2roBXPVXqRb>({}f&MM7qydLqJZ^_IIJ0p6Be0$ZQiAaY`D~UEFr}Ib` zC~iK*X&|6R{{=k}&PAp{%=BP1hwD@Ozk#eyl@xh`hOkRvQD=q62LV}&@IgS931oYW zFK!e>$gaq`8SHFz<$Ny0Hld0htSJ4&Uw1ysPS`ro`V#wvMA-#5Dda#|1Wbvlk9rO( z*lT2f%&ZtEuN+4^-D~E`fO9r!Jl;(n{h;67i7Kod*p2%MG`$rSw}342-A~*^*~@^G zA?hpFDG$f@SBfXrqa~Q1Pl{xBJdzZXS?3WWJyL*q*t!>D7&#xhs*p3UWiPaC!Xy!U zC>0UkLE1r5tCJT28g~|3ZmT@ZWI*rR=tWFRFibU%yriz+w*4#{=1lj_>ED&%- z*^s-D-?#>|m~>BC-OtC40n;mkJQ&Q+3T?;ihS%^Z$|oN-wQ zK*D}=b*O1yX4uzKyw5Vuq6MZMDRJK%#sa}ptNZ2&{(*K$@x-QTlwKN3q4c|1QoZH} zyJD}0qAN2B!1$|F7BTH+{8jSUW{EpnDiJBjTXdm20b1AkXHbZMW(g=NZ{O6WO{X4g zCJ%*SkX1AVi`e3xSs^E=uY~Pq3PnkSCdRVSlK2V6(`BSl&JXZqeG7_J&U?y*=N(8UF+IiVb6L$bv5e@v7 z(2x^Cuy7|n6Rq1I@2Y$#xXJTF)FvFxAF)}ZO#xo%gf9$<7QE`nQ zD|d#fA(U8ulf}QtokNvYOY6S`5tPD1tgp7%{_%IIK6!`AQ9=%DO=Nr(;tR>oDETRu z_2(C&-c-a_Wez_hb85ep3K#mhvl#0cZkSnEDL*5rU*DEmMJ%h(2kzGw2>|d}6WZe> ztFW(IQgOq~+GYu?P0DZm)`j;ZyQ}Nk$)9NF$b%a8JDs;}VV??XC+%gN^D1bQJKEz>$6b8p`OF2IMJlQR+@XBD&Pk1k2+ zec%?*Dd5DGRT+Tco`?c1T0p0YMaM&>3at>r8Y*^m@_oz{XRgmT^qaYcCxWV7D{xDD zQU-==x0k4vY!LkF06Ma6w0UgfeDb17m>(WI=n3&!p9(`VR53NC_2$8_?()V{k6EjG zJy5W)l)fK-BkUV=T?bI4V%@bO?HU%bUT8MPLadP1S83-T-TCTTMaS};yq}QLJxuB_ z4ySA_1VpMmY|TVdw~cZSIV_-Mi5zt}5ZwheOtI3x#^ooj= zE63mo<1cg4Rn@MQc64DVYqw8Vy^79)JeM`D9VJkkpkJH2OZnrf#zYF-OPQnAI^_rx z;N*?(g&pU>kfVh-jLiZay!_PByX+5ItSVr)Q2F-#T3>&@y{qrMNSsvAwak9z~Ov$YA}ONTbL^(22S{-}LM zwEBxG-^VHI4pV$0wN$c-f6S1phw4=6XVK!FLehdEx=PezY^DTiNC%{FVo;p;FEnVNv!4dsrl(?@qh+Z7404-k-Yc` zpDiuz*bw)TM>8`jo!oO^^P~q$clhgHQQY0tSHkS6Zu3_d$H%*6+}HG3KPMNJ#utiT zYq-7eTrhy$CvZS}&DGZGrf+i8Y$s8sBx)kjSV?q?O7yzl+-PlV+LzPx%>YXBZRDV{ zcw<9dVU?f~Q+`}m)D+!hvjcv*-PrFXjGhdNHxFvp3LbW%U!s)&^ZJq$^5ff`mE=f^^t^jsuF-2cRO>Q2QCXC)x)`0+Y6mqXv3Q=}< zjilD%wf^EK3up7~FMg)523I%(w$?l{SH(LK)=NyS;2x&d-(ctUQ;+`om5^4oOo|7# zjAL-vT(C*WaK6!@aYEUiGKc^8HTf_2izwu)&f$Zk z{%{?gp)KPGhcH_^zTqn-iOgX!M)tg2qsjH8*>Y|$MP~B2 zFa7yt^Y$|UumZG;8A?0+W50El%bctIM5HIt>QAtCr`S;0U-bqw&B%QEkeE(Kn+|a% zt=pz$G+MnaQu>a+ZZm#Y(lb5_33q%uUfS1Axu*XhhcherHVO+7%}GOcrR%Tr*2(a2WAR6vx_GybtasTDmb-#?=VUAoqldH>FT6dFF5i?B-LgeZ@QBbfO{a zKyn~SlE=!=k^D?sl9W8jUi_qTc_Zfky=XWvltn}obV4%@6=b|_MUgD*+$!T)zbEXE zG_!G#4MvtR7u>dtU+|0llS}D&|6+~w7hDTQ9`!=)2Y2-b37WoQKImP@E^c=C7vD$M z31d30El|)`Uc5Y&BEB^JC+)P2r^zbb#xMPs?PS+*46J~BtAU(i7O$3%)kHRb>7UfZ z$H|R?h|`6ZPHzirJe&pAYH15xjps}MMSH*W_l^l)&Whs6iKqFCHZkn0zGPUoGA!8t zkxXnK-RH)F$<=$z;@yJvcL-~i9+aCCWt*;%L5b->nI)?f_eYfM-mF!6g=Qx?;p&?a zsU1A!dsB(xJO(qObo!y^xXi^|v1MxHOJ}NSQcJ&yEU7ysB13};vLFSwW=Jw>yRVAlcC9Wb zm%aQ&9-S@n=xps;A;X=PqfSJ67=j8nTBeA!Xk9pW6$*3Jk$V!ZG*R~L&!u}OB+B-P zCqBkDem|=lvwFdBk1-XDFOo(Js)~Dv%>%UYjYK<3U%7GkDNM z9Nr*j2bZ~^k~fc3ojjs>i>mXOw!o<(_y-v()Z3x|+6k6C^+#l(?Bx4&irOVnzaa{N z%b#bm0hF!V44KakLQ|Bq=aYc9d~U-@Y0S(++}!cBr|a;i8|3EG5G9;Ggsi&_JnW$J z=zGx$Y$wQPEEX_O&T-44)ae~hv`%6A?Wqjq`0L)K&k|++8Fa6|Zln5Gzz&L!HGD+v z1)K?7R$u~Ei4bymAkPW>gQ5i+o2LrXGoKe)--fCUkk;?`y|kXx++~aXgdgnQSNX?o z(3d`sAc@{^FZ>@bomnR3GMs9a*xXV3UPm2Fj?!JCsx1T<_p2}110@te+UhqqH+|d7 z*FVYlC8ygLam9BtvdLEH5PbCcb|3?`x z!C)&a^o8`u*a9^3io#Ht0(SE5FZasGP|4=es*?-oI(oHC6^_VcnD*>3?!B5ZiH+nq zfWr0W>OUdyezzZO69ld-DP42@MxW7#c9}MU-(Bu>sSg<$GT--F|0cAlY&}&ldK(l3 z&#RC6CO#mXKmD>NXdgsqVfH&jwR$)Cw;j?J<1o^a6mIsz9v$_tkRcVX7IS;It0{Yk zV)UBkUa8Az-lfXvAVKpP=YwryV|Vq*})0 zkn>7R4TwBocl-_C&?yv@h6hS7@F?}-9~VnJt+b4-L)6w;`mo>q3Xvf)<@lSm#!iM; z@}=tY#1CCn{X)F8WQ)aWv@Ad<)nc`%gl+Yba44}Yh(yPicu$(4&vfGdmg_gWWHwd5 zq}1A$Ue0W>T8ei^?Fwa#BdFKKQ0h72TrML;t?U-(5?``cqE$W+g_FFJmZ+WLfKDe%hNnvd4@ZQt`k!g1~tqLgne%DVK z;6639pQinw0wD%dHpsSWm>ue%Z6Ig#oONt*W0%Qxe{EV<5Z+H9r2PV`a~gW}yPK|3ev z-Gz~hA;+}dPXg<++7D&P;EqMUZ=(`^%*m}?r?x2{D-DoVq#VSe+B7~t>I4V6pHis= z_*`fDz{JxJPzuDBCxrA9Ydx*cL(SPIXE@W3(@&a}Dh*wIa|io1OQqJ9#J;=*N7XFo zHYwhxWM)iSDCvYs-B>}y<_uq!;E>f?VKp*w&TCCgoMV=%{dXtUP{b(1^y-AEAdx{o zhe0KcLO$c!E9G4M12+Ibu=8iKwp7?-xr)@9x{5S7UHY^4{%Glde5RadvCEJ}dJ?rL zJoOoqs6>JuM?fL(VovHrnB_Dae&U5g=^x8|_;4wwG-DePyQ%XCuZ#j@k`uh=HOHCE z#Oi==Z%kgQJL zr4sTH=Nxw6Gug{IdFM*V>xAgF%E=ok0Zl5&C|%5%60%c;4ALPdNyx)0nYM{(*+W9~`A8O~{;@E+o zTt-7M;#GJkr1S8Q{@2Wc|to+ zYUe5KEYr?%?X1wwN^$JKU6=l6NRJ??dpD;Dogl5z<UGJ?cA!JMcQf5 zj-{R3v}0@McI`OYS*)Eq#IXZkPX5o3Zr={1Np|m@AiYqRGf_K}v~!VmF4oQ^+L^4K zOSN;Eb}rY>6zyE0oh!9dt(~dbxk?;6uo>ame;WPO0%@o#q$lfgPSMV(+BprU#gH(; zlV;;2@lnldm;C1r*(%k%E&4_sk?b=1jIZlfT)77|qOEnk&7uDQHTK9&o z)jXsN{;PKWrk%gzv=|bmt9ej-RP*h{|G702N$cizt!5gbz<)JPvoT#2XBk)Oz-kpZ z)eF2#2VSlMr+9%E>%dD?;A9DGHZH{BU8S<~aMcEnr)_+(!<&tBB%X*;+-a-YeCa1| z3JSWs1}Ci}sKaY;$Y#8U;8Jm^hv0N(?;*GXPQ)`mWzRXo*!>CZW&NRhKEewXwv&Y1 zIF|8bS>~W3Zw3KMa%)9uP)Zcxep8tGZGTFwRd=b~IiZEKlag0ZSJL&}f z@`ZF8f7~^I<65n66l{#|v;zGu=_VX&Cl(`Ob=sIXc~?`6JMn!sC&ne9g->rv5^Kll zg+KiQ^@f#*S2Enlc>2Ry6Ivl(qWA`{@Fx!bTFO;50DA-gv1y6tL$`a1-|GKwIyA`( zoBi&ReYnYL47|9T+LT-vQf&yS{Y|v`1&;j8YS@hRku08}yDzeLf5iV-lL#*1 z*eD&n@YU!;^$7XagwSjBHYl&IV7+b&)q=I0@b}{MP4Abi&AU5ou0qx;j61!rRZcir zG}?+^DfYcvzq9YXb_xXC%w=OTDySo%-BNW=qMWARA!=b^;szN+faP5Z_o z>xO)5xl(pV^Az2#(XUAAG5tn6*~R94=HBm3%8oh@2#ZWpqW;LXNYi({%-SXu(zG8z z^|s>m7EFEOd39(b))n`=LBw=n-zVOMw!2femM_0FG&!XAsxW85ULmoVmbyAO3gt&# z#!%6W?|OaiSrPr|gUVOoyN;N5U6 zw{7zM%WY9ich1Y4#>KWd*LrtU{L8=eHT#!s@@=a9r0pDJ<}&nd?{nE=U|jufVTSqq{^q`geExBj(P~-4{`gbISIMgu3lD?Zaeda1s9~cm7aeN4AQ=*)7Q( zX39cMGd5{=#NW^`dx9ktEL5YpMOTy%%4qq8Inmi@$*M zc)d7{M@p^PsZR5{M$JdnH`l7tD?63`vqSXF03l{x-xPM}8?G(rzTrIXaMdpzyCu~* zDccJhR`!$8J{~xg9_egmC;`&@V~4Pn=T3F3SSAgItJiDWe0mZc$8-t#UK54lkizVE zE9TSGkt<|exnXNezB^A?{MUo9^m<`YS^CS zej5*VZbdBlRRiamy!~Ft6e}DtJa!_-n`#r7%;t-0;Eld@=qEG7GV25>a&@GU+9lUq3Tu7c1Vf#nf20adQ0C&4UdQdT52W?>K4ld@>umrCmIC-pp zSr%1gQ&n&OvTmNY8@H@-l1H1iHeS$^#Ua_dSF)d3dQna+2%<#P+ivkM6sb%O#Yz=QHi$-@zzoS}Wv@;G zhc9Afxci29+DFO5bn5ORx%@D>LBY{+5}!&|Ch>_E;&h(GPg2HzF^QLSJTZiro9MD| zJzL(7dVn$K4YR2IaN*-J{MK8#FH#o?)gfv1>41Sc;F5GepiYYV8yNxlCwBio;d90xTfAB( zdftPoz^HGZ>s!nEW85m=)f>5geLKG9>^|jKViBv0*?lUwldFBlI%c+&cL@m+jalZ> z=IkuRPx{bbGv z7UNxh)CF@8Ps`h{QDn(rOe6VQjQhQkS_~)cyV3I{ z%2rgV6?uY>wf^}FMDcndaqh2IOS`7xb<9lKkB8`P@u(<B}-yqVoZjX^CxORHMIpFro4e}mWFl?>%hc~JQ4E^o-(tus=BTdrE1$?E(`-n-Xh^vvu zD&@Fr?IYGm`?{rl-P69Dw6CWXIE~^GjXeZc*fklsd>&vR)VxjGs9Kr}Ka)u?+tLR& zLFcVc+Ao5}j#(*8C{a$=sCdt2B=XJW`Nt)z(gx4kjW@uv@7)C56`Ig6rX)uBB)hhC;b1@Ak+aJde}hD68EDLRx>uvF-@3jcG#hz>r?$~$75AVwav zNE`d{FOQfFxxC0RO!w|rVC$Z`!u9lBe(Yx3_~hJFUv0-nZ;Vg()i`D)^$^xQ-B&){ zSA;$1nBBro&_H@VvN=2R3us70IK#sejomHd4ppq`=j4$p*`++py174!+o>9_fY$m8}t6ahy zlz4hQQ5iAr2z=LN9aS^OnbVf!{liaxATB)E7z?Df5$1O!tJ`tIjUjmHkzeQ@`Q&wa zWII}aq7CzN!caN00|CjDcO<_oYq|1ad{f0_b0(aQrrHY?#Z48MhAnMca8#&x{qrBe z`4qnu^6eYx+?4UHQg2S?gsr{m*tWK99}QU06V=`4#h2_?-!`A?404u#EtPAHlf0W{ z9ej{F^q4%1k1-jW9+N6PCShlkq0!TuZ{Uz&<;@~_sPurm9fzR}b7{C?1Xx;GDgDX& zY+4KuJ1y0lCxD4uP-NSEdj;Y^S;bLnn1?@Fjqd4(Zhv$}l7l6cV+<6ad`%?2yhGwk z+7s_Y{{fxAh;Oa0?4u}v6d%Ck)e#kSZL!{~?( z=b1#b{cq$PY&^iP9k_>I(3hu#>hLjG59(5;N-5`1if!CTgzld{HWga7>6fiS=H zXvuKnbbR#aOS(q`8|l%{c^bhsiiuFn)zH)MHD@0&F2!6OF*d!}_EpT)fzblLVy?RR z60c@aCq}b+dp&Rg(Amb@Dh*56Yj}vw_u6>2g8gX%vH5>&Ia{Km8WY<-%rgF$AUp6! zeg_Bcj8gAW9l&KgB z4P_(1Bi6pyY}+U$+hF4~e(k^@e(9cq;YJ^P)aRqmsqecD)VBeJQr?dxLf4m%uM@qj zNYl$~)jjV=(%qBk3tOKAYc@1`HDlPcP7Th}q#11dgI_!F7k;U|Xt;4VK5Fl!Yp<2s z$4Tvh+ler%*~S`o;%m;%8Rv}FsGbvbMr&%Flk1Fz^Sy!uiPf4V%Ga!KlSZ3opppqA zNPJFWLxBfTb_$|m!Ur1z_=W!Qt0Bt52SoieL|?uFM9-n-X$QVBQxIj41c=Ii5TaK} zdT5AFQV=~#JP^U!YzxVF)kQ(h&P>fu1zl6B7~(PMMMfx44Md{#nK)kL zeDvDI>0YZ$k7K?wT2uENA0x`FBHe6fv~W=Pe(8)BN)&E5GshVX{rLq2B~}Y-i?ODB zH3yr*(C`i1j7}Km6Px1=bq@6SS`E_a0@Bx_v&p@^5%lA5(ghpN%(RXE_}GjcV+W4J z=k3If#mk{RJF&SGi|wp$E7^0LJ)8n8^)i}0w3QI+i9gZzoSEV0_IY@d^=nxGqTKFt z0-FgG-QVDG>ctTr3=*0Q&-^?Q5a8}dE2jKNy`=qyK(&?;ahRS&HKu(*k!s+6xBr z$l(%np!R@V1Kg*CyN?bNL`0#hMX60-L917@OuczNP7k}MPY(%7 zv}7he%OoT_mX{LIc04^)z0!V!D19Wyi>V_-U4nOn=;3t+tr3CR^a0>?9Pef&aB3HP zae~l6G&xM4)A;MIWdfMb%Xt<2V_W1Iu^BBsBvbuqqj!wsuRED^+O*LT_-th%J0W`hywYbPL+j2~$~zwzzdqIb7hf;$ZFe-tc#S;f2kGdR2by zL;AexR`n(4RlG5$=Slj8c1j*N$$Aq$WH5c?uX|rc+pb+I-TW(|WKNI!zp`;GpOfe>{uwuvqD%_9O0XDJboVmz2L7)!pQrIgwbY8+{ln2 zTJ1{*O8U%D&AzO;#gdu?oC{@1IxgI*q)uQ7h3n>iNOO~AHN(fnG1}-uLyw$;=bVgf zib5vOyT}`p10)kF?$L0u+>(hRQbvdFjl@2b<1Pol6shQRD4H^&D_eHt*XL59`D_X? zF*JCq1f-1X&L>ggX?<`svGdseoGXli06pxWXM!`8@KiP-`p^lheAwyyC$N(6NDQP4 zo+12U`s+r1KU>b7HvLrgE{EH{mWojE;TUv_8&PAJvgq;=eXorU<;3!Md#hN+uwHb_ z^=w%yV}qKu#xS$gjUU-QIlzC?duZRe6@TKLATc2Ye51m)**82q?@geY%W=q|CWTy( zlA(7a*RhAYu&-+0d5?iYm0l+#Pp|`PikNL9+bm-h9!s@KB`hMLH0?4rk?=pI((1!> zt0j1%C@y)py+~nm%5Lxo^`%AcQ&hinFrw*c+)d&XT`U7z3ZrWjMuQU_Fxp-6(~jsY zJPqjl$=d%5bgrQJ{|!1fQMm4~KIBNAE$G}wp>rNRk_#gDee^#dvS@j>BXUZ=upmWP z$=(5f&F52u6-@q)%2Vg#v(>5im3tLI6?z)KurnguFVB~P1$p*3G#a+J57Ny^9t~+n zseNMF>qn8|%vo}kMA@NT(oUF6X@E8iS3gTP#ycrVk)@*aoTM>q3qk5pSRqF}_?*_? zNc%W@5!imB;{40UO`n$dFes<7i8qK;=XR}0L&d6REl7K;y~LEs`{9(tHwF5Q0pA;5 z+K-%Tu+MXD5#2BEapCQU_etfQD>m&=vg0z4yd#n0{!RreJpjH_7&%fkN@FBPz8H8_ zwL9gRjFr;QP9Fi;#uI~>2FALZ;DO5ifL~jQjGxVQnJZy*c>m=rrh=IC$R8?m0)too zBO!=07>P#^*d)sSsR9&b*b7A5%OiWKF^S@CI2=yloHZ#-d-R%2PgkM+7(7vSy~=;G zYca0O_e9yXXxE{kA2Wj1Xa= z+HEx$Pp#RIgVejxU$I&RqO31rB2n)URzTQ`HJ!uqc|hVtk)?M?mm^^Xox`#vtV?;B z5|-(uo0j;%C*Qtl^)CR1?)W{S83pmHO*>Q|m~*uqI6&8iZR47oduHsd5!5IspwEkW zXgf20e3+eywPYiqoUL%}e8uBeusP3P;p**hycCjrpc5D=$uN#d%?6{%gUBrhF@JN{7IAQh{SS z1g=qm9Cn=5A@C0>5E0J64uN;4Qs$-uMFoSq-$ckCH-7e?3GNC1fE*Vu`V^LN5zz`R z8TU1|@q$D&A|_aiXAUPk&){mnZR275h%+)Wh8utOqJNi;w(@#VmJ=F~*tXL$>IvuF zmoo&#b5mCDXQvZ4^yUt}o6~@;7To-XP$qZr8_vsWM_vva-A&S$O*pY*i zYWDS4-EhNIQvvS09xb8X7hF4|dRj{;aNX4A(2-Y>`XrV7g0N0qDH&(aoZ+Ru;@TKb z(S-VCQufhVnk1&IogDg3kg}S=x!{CG=(|&Pl{gl*khAQ3>q1WRgV_z0#=#14DZ4M) zPRK4-Ot6cA1s*nyuTB^h3{aLH{|-h=#8SDw8l(<=4|zvvc>kW{oil?LHEK` zq<5pLhy~8HUb5bleI53^P7_0ODr~>I`)XE&&wb8>Q!+SsBk(!-@sItIYwhRmluf6| zho_T;E0z$(s>?8Ao|R+md~F|hcqU@mxNu;@c=qjF^pV+AgSpv^*iVy3R{EDk!^c-P zM6)tQy<=p}O<^JiCO$rZ35#>TZqCXFb2-ePKn2y!?g@v(Bvty7Ig!%!@ik&{fGq#Ak-mmdHcntljvA0f_GMm21wbo8^b%xY^-7!Cf2pB&(`Pvi=P}JPGn+$P zdvfOQv<^&5lvTy$2s^6ehR~pGIU!{mNp#$x2sK=YZ499bTDyB7rZ)9KG1{1)`Qq=Q zm+B|;w2+m3zG%{zU=;WmU&(RK845d;Mo-9kG1T;7PE%{n>Cr3>JYPdB@K#KYqjS#- z4?#tu(O23$cT3Vxsr}1eXc!}j8^-hqHr&{=>5CkkUcxVOO68GQw z*3rijJsB^GNywzRCy7m1nOR%hKeKjpQi$wue-I1Q9WRi-kA#8uQcq>Jj%3Eahet}d z3|#(C(XyML#Y#4HISeo+5lxPNGq*JxJmXaiHvnp#IyF3hZH4&{S%y;+O)D-J5LEwc(n z;^^d!AZe%AcQx4-io}7SqV!Q>8s>B?pF9e)XMr{WMYcDfsf|OV>6m7 z`!kwj3kaOy8VN5f(k z*?|WJql>E;NVhkAAx4>b8yH{Hj$TDOm>ip)^4AI+>i}pqHK!fiTAUO z>5Zbyb3UK8F-7@D@IW|tp0+^M9XRVKr0dcB zpk-a*Qi6QA_u*#X{>J;h-~0Y8ZYJTs^S*!YegBVqQ)+M($dlo=a_GfZSbuHhOPz3_ zeD&vFs8j0S;IQy@UoPh%AGlku6xFLSSZ1mryPWAhqp;atFQtv;$TsTyJ%^&@@Z=mm zxtVwM^Kz|VD#jov;F)8kLVKP{@}c{@&Rxf+`#rU%$REf}fikiW(%!Bp)aA+nsoY0- z##t7G4juV^vDcBC@I;;Jd{w3ZvHL54Sas3`;mU&Og#~hrH{Md-KXUD~>zd04N^jmy z<!@1J&*CQE>=Q%W{HB$t9IX`R%ANE3l|0xap&)^py0{ns$ z_($SVtAT(n@0r1s#(ez5#UkOnOp2dBcvb}a z_A`Wwnf#r*g|5C#V7M4Q-)&dF@goP1#(72FXqb3b zu`^frG(*+MYjJ&KNSrYf()}Abg8sc+FvzjFOsFp|x}_Sz%>vk&=qh2^+_!a_Ckf#Gu=^69U_n2Y zVwr14;daA4+WS66mgvw*(aaEXEHw+peg)9~dC1Rt_~1S|( zf|gp~Bvmbo|EpRy{=I80Y|NWaFks1okeFx0r#KG9hY9p&{a5AAJE+{Jg+$82G37tC z9hy(>gz+;f_lW_64T7xL&RQ$Pvx8=@a33p78JE6T-om(B zYLkmEY_$w7+dGrdtq!=WWRPQW-)fgz)=!}saO%eXto|x-ua*N&XADNnY6NaO@Vh?X>Ju8_b`s=Ps1$aIU`C31&yz%pb)qE- z<7llrokH!QQ32I9UMYm)%S(J}c~kRJZZNpzs%HBE@%N+X_IasE-WIopw$KaPDL{|x zhf}i$JpGFFztR3g5WTswp?-Sl!1LVfV!jlwO0Z1|HuTQ{+w|1dplX zOZ8V&z3A=^R;jy_dT^izzoeVs9-)&=av#y2d3NA~pK~yHK#a3EV{YQT0r~ubPv&wX zjTTDZge!A=wfDWo`xaU#@$2L}wDMLQVb0YPFLYCmA|yua_*B3D9ABq?*PXtNR{rNs zKklJJbb2Sv-rDUAj3^9-D0r1n(KDDc-0W?Ev=s6gI^t~;3z z(Q+6^a(0uLiWzybsA&c9aDIsTLul`!l)%nk{}!!OFUb4rU&b3rJTH_IkM+BLrf)TM2HibL zj!>ON!psOROd-(ZKBhaHc|`bZFMK-T6C`{R;Z@FKH5!sBa?oC(l8&9JC+qDLh(+Dl z(d3w|cUbdP9b>WO_n|vW`4`s!t?L6yHdvD*&f*8;`{z3DA^fYplYpQOXq13{5^$Oh zkol=o{6q&lPJmu5#p3m(Rowv3y+XmYc)cV>;Tmh1j0)o=xZ$JDjS``X-An`ln&Xg^ zEd+o@izF!nt~BD}C$Z7FSCpVhx>-{FuS&J--f=qY9SL&?OCIN?(EF@k>e3}z2h30b zk8V=|YG${(7Z3()Q$5&Ri93@YcZANpT>^wTa0e4$u2g(%<@HxZti{Sq&&sPRLgQy& zQ7r-cNfv6pRd$MkpAEP|%`7Q<1!2;Fd_H(~4?)!hZN80W=_~PF;O`PbH75TGU&MK| zFOIi7w7UOjRWL3kO#1Iwi5o_7?ouKMD3Ac!li@BRz=JV-VHl7G5oN9{C?8cdz94cf z0jCf!E!1)=Ht;j1T?e#h;}K||!4+tQIHaKs^Kme=YYElRuEPhkGF)kBvoy3D2@_~# zmaUp1zIW7_Xtd7vb%uy_4{E*WM|3bF}wz zygjt{GQ2&t_fovQls8OTR3>!L}aKOd6MEqt;MU-0vY^W4J( zgr31B&^ab%O>v^u#~3wDa#+V2{+Oh6&dr9A`V(Hul{beu?P|%fJ~xY#6tWPBX?f@< zC_`?LEoQoRT~Rw&yHR5=={7vT5T~E5_19&xKBZp&@r(){(rV=jW%|`$&T6S1e!@L? zeY!)|r;z=aU>2{=J}&LEZ;{o?be`-c%$IG4&FmA zTOgCHLP$hjkhLfDf3x-jg#ZR!-Bsm`Y6N9=(d~VHMk?Rxx$|y7MYlLQ1uADIwmI zYJ#&~De2vV`b#ZUztMhgsrmpt36QP^*xfiKSfBCrfP8HuhI8r#vmh#JZFyGDhy&tO zS=*!jv7fM-iHX?XN>ZmP8&U4B@N(x~=0po*Ag99mC|djpMJpP3$BxlNXZM*>PP>WXK(68K+!uf#&d2o}vtY{YW^NjuLSM^L!onZg>dBgDCc_-pZVtCn39ChpH2 z=2nm<#mKGZwzIWa@NKpAI5d2RYp&&G9r=(1n3U)KK?gKSKtF!mU+DnFov9QxIzY{!v@ApM zX6vZ8%2~c%@(RTiqJ2K8p(VmruwdV@QQ{N}RH}wP_YZAbtRxpu}-fA`lC?*9_4jA_$N*K{G^i1bZ~ZFhl~e;xSgV zb3}^*B!(ke)L&dUqD2Gn2;ebX0X&Y|32@DBbOl^OHQ+bnO9NiVN4l#&eP03IASD9l z2#K3XZSDpl2#~&7H9`WOCBOq5-betyn>b7pU3lHh_^hj7juurB0NfYgc+8P3`2z7H zxSBblx1_6|BtYz2_?Mjr*BLEsQe3BmY6j^kd})Yx@{xvksD}6}!UW=nByN%bc&vt4 zs$5kh0lf}_*a!Cm#A$YJ(Ml2naTAUQu}J#_Vv+WBg1A8P9t5$3YKY}Vnvj-$V#ViN zSS{HUihnHrA^0Jp(*It#76d3MOI-$)!=Li7$=-F@WJRQFD1uC)f)a{O?Zn!!VF z#d<}^ne$_Ww@ne=_RaKvOL(~>z<9plx2qJ{=-aSDYBox`r zQfdFzo)*IENZQZd@zS;v-nR$}dW2V<2dC(+NU!Kg=HimDyD zJ46wHyM^u&KfxWSziagO6t}m~11CjyCz0Fq=@GabMCx4L!x%7+m#duE!GoDQ{9=;`_HsfUD<@3qyhG*uG3C+dlM0HoOY_4 z(GKtrP~G|f6+(6EQ&hM9kW^OzPTc~yrn+4L*Hl+zK$_}$fTyXh0R9Uw85-kJT}M$} zMZ+c$0pN=2DjGIQa9;OxXR4c~VfC8o*8eEg^&tLls4jeAI}KZ_MnTiCb@-uSQ=`tq zh40|+NW;Qbo4@i~F)`Ze-nB*A03ZCNYvI*)K$|15!NuRk;q6oOYn`TFQWV)z^lLFF zEA&g(k)~fdmGHr7`gJmeS13kUusTJ*;!Jj#_UcI)usdxgzsmYRTS8U%?ml%Vv&&eO zC;PGK?8i=d46hw%!Zbsu813eYeE8t4_rV8aho_>5J>xI{#VHYIxKTiId4t?6vMr&H zo=%AZShVt$AiqXpiZ?<#3qrEga2y~YLF$Fn{*3j>YVOLjr{QGRMxs36H0?{}WZqxb zL+a-3k#RgMxbQwSYo2T4&;GScmOahLd$&@0qU?`7g)9a-TzPIku=gGft~uw__|Zrk zIIj2C{h3@G+4rnj$mNT=d->wJ$)42(ymf0EZ!ML>3G5`HKC16BlrEL7z@W+zruJeq zUh4*b(JHr-?FMN!TH9uEXk)AOCAN9PzR#pn2IpOYaR__`n1sN*%05|9&SsgjT7I+} zTHS8HLQmj;rzhaA??JPj-^+fm_TQ|eZKb>CjIiD>-m9LZl5;Y^pb@*=zw%be4;TH7 zB>DRiln0Vn&wr#B*7F0rofszcwR1ds;;pAp7n>Z}r*zunq}3D92~-*iz1)GE2c%UD zet$2>bF2q)tdF;K5`D__)`{(qr)414gzUVR7&!M&q|jtWc1x*RHaNGb=BlT7t&JTY z*>EmCo%eYC<-ABRHeas<`I`V_O%|7C{{3BIO}6*mU9Rq|+BHJ)onOl@S^BykzA1-F z{pd0dk^TY<+;CrkT?VyONEa2TmG?4qmLv;U`8YYyj^T`EY{4p*VQ_y>2w#P(R>;>9 z`AUl>O>1ninU+iPHKe(+6eXHqW^-kThg>U7uf1#F2gfUZa1H!mr6~(QWkhz3bW^G) zUiy)qxL)_fmuWz|3!vrM*m^`H(Jk@GcZud~j)8I^PTT**I<7Dvqj%q0%cvaCg*l2O(o=FEs;(H{%Mb_~mMI~m^Kr^u; z$W5yegbiY@N&A8#-7phj-l2)qwk(m9Hx(9T`s-T`Kwl>p4zo75%Aop}Rb)HkHexf+ zzew7M(A;oFZ>-tRcb)pK>W+!vja;GXF?Wvtxkjh1CZ9U|&%N$U7`f33T=8495uaI*L31NQnoB!{dHD`Sb%Br-?@6&Yw@!=G&>`C$_xN&=);C&^@@VAvZ5qQ zPy!RU%ALMb!@d`#IT^9d>Je9KbMo+O0(Jzx}I(>4oD`&Yu5h#oiV(F?JGYG6ep((WPON0`G^ zg4p)mYgPyzrL4Ji!F0q(uZFC*7)s1rvWpLD0YKU9P>qUsc^2DSbEUAC-)< z%Q+51-~$DXi^7<$;tJbNG?K2kJEe+S^iyEQcI1hny)8l^{q;P3%--?bffd}T!LD06 z<$+Re26ZzN2L{ePI<)dQQt7hd=@jpfBD80%kLEd}RGkHI7&_I$bgE^fVmXe>z0o_; zp$`z6_}CY$`5pv&LyXikk9NXY1I^Or8&2Yj#|~QAu<2rTQ`mY@#I$q~mrB7-xymvm zm8GKel?wmZR}ifO5J)L^{^*3w|7hu4)Yg+^8!Xv+5Rg_!?Qs7T9rCx*6=X@u z_W=pPDQAe^stRVF4Vsh<`kQEjo#NhwLYXt@JskhCGpt>0Z^`Mf7>E17eUOY|bq8!l5uL1&~4S=^Hw;hWnJWD8}g zyO9f%W0<}=e9lm2wovOT+32?4t0V-5GccTrSIUHNpBk283-&{KiLTIvO_ltDqqtH8 zBh<K%+HWnkv1r96hsutYkF>2Tqyaedn#yY}i5bvAnDALH*?P;q5hFeNaIs$}xibQCvw~!hGbqZ|M({kG}4U`Xicxy{KymVso-W zp0NlO|4We2w~hstySlmG!)m{m%(ULKHnU`eGh%0{4U66wp)|m0OW3`e;^o){^L07` zDMZx0=exGH9y?KX5cE70KkO|iJREHGQ>(-a824SdjVWxa>$tK)816$lU}-wVy*i*E z9dN4zz+*1J)ug8VZo#YduP+$LjR5%k*I;d^tCuczp<1ovj?M14I-m@a+v6&gy~y?# zBUtn>tBAh%I{Wm*fjs|Wd0nYyK5vtZteMZ-BqM6(^ESy@i5-VQdk(WsPn3Ouy$05m zZ)tdo@jf49fKUgXT#_OSzdw^_3}y3Gb0Y&@|C+=j3%opHZI2|@=Eo*;O{AhElxQ4? z(kw{u06E<&zCpg%IlMu_|4g_%m89PKS|A~3cMO>+A!3rcUF~MicL?m_#{kkqA~mJz z1xPK%7GeZiZIz%3b&Mmbc(-yONh{Myx1B+cJAqm0fLC?EZ_@#5PG@mDkhS16?vrP+ zXmAm|Fb(2;By;cD-vQ`F67n7)oy-=j=|ZUIsm;WCQDfqH(pSM7!3w+P0e%rL&s5u! zyOgW<&F(O`F}WuHj0%Lf7iuCL^lrz`JsOJaU437e6ay>id-G@F|ABk+PT-Qosa|`N zhAP&lRU{7WrCl!BHGS`Lk$z%2?|tdK(t?mO+4U?wA?}LEv+!L+Y8Jk$aYPa_MRkE3 zE^E^MUer=#*&{)6tv%b_`U{avoiVB#*7!tR$6wfA@uk9Ue}&tg^;4nMUe?*qAp_vf zdbTfC0HT)xsYObxw6P* z?ctR6jP`s($=9AKMU0j@LP+210LlQ>iU6&k1Gy(YCfCj315aPKZFai>#4Ebyj1|gg z+E1#{E^fB8CuMx#00ix55CTHR^Pt?2=h}uxucFYaS1H+Yu?!XqBn>qTCOe=>i3Smt z_+Sld*9ro5+R}GH&`GGhS$7pagj2;^^^W$s|NJ(Eg%Sf67X81$!Z+;{7M{!5fyP1) z!9AS5^aruAWrtO`%?jztGP|V+>>q8Q?BHQ_i2%kwOZQ%#Jwdua`Rp!peja zt|5q1Roy6T+TOQiX)Di^(520F*>Whs!(R15`{j;E#B5nDEE6{aH66BIQ(HjOIkPvm z_1Jmv2|v2`bzWn_08YJp&@dBf0_A%PXqDxv)$%?+x&S4Hs?iaXnMF6t@8-#iOh z(Bn$Bq=X=F{ZH+Z!Z})ppXF>K)4hl^+^>P+BOPO?Zs1$;hH$Za)grp@-t(whDxlF! zak$nO$-^J=MEFsd$!Knf$6T+cUVA$KG5D{OOPj56uX|3HmE@5eKk_l49~zkN~gESTK_=! z%FPU%yFk^OUFcaTkJxyfarpqBrN*%e{I*9BhV24{i}YmMHT2*jIl1tn7!#HQv1s*? zL^X1hlA~fNEU`ko8Sx|S8p$!-So2Y4#@rsP$Cj}IuhclLrAEX%Ql3Ckqwvi8Y^jln zBY$3GLFdn+&YvZnKZkYrw3i3~)2t;+aHO!7n!E5ZCD67~^)Bs@x%Q1gF4zosE-(34PcSPbdsN*bK_ANek+p}v{uol zQQr=1l|x#TP^k7(IVDIQg@n4NYfG_giq=40GUkizd9<{uIXkEv7=@^;3yYa_d4(@1 z*U6Bw@yKFsp}S~cTU)K-9^^)ZX{pI5183hUlfhC`!nA6Fe6-Z$^DBRPWb?&|SQ83! zA{L{YSeql@&YviKqRW&ENFBZkJJc@$kY4C)i|t&3EDt%&Auq#OQQ4u^w@SUXt0{EHs~$dEG%o-n+QEfw*yX`x`# zF-zR6s|6uXE)4CdD9(<}))akR#JPD$#6I&qx^sxUSqq-7cwG&B#h%LI>^QojkIFoj zVl=~*)46SlvhF)&k`8Dw+P?61D8??&mt-=p;knND7v3)oi4LC%%=I&%EwFl%IidPo z+IkaI-Cy@1UPfxN^FQr}LCmAwpUD!iPMQVqKbk{U;}~8B$~22zO7lBw==rF9M#Q<0hnn`gb6%4k z^w*2(Yoa+b;zWmL)RY!rK9T@+<-}dHQLzI4x(0$oWL+ZG=tL}7QZvt&5epq8a7SY$ z%l)`jdgImt1!5L7LVThvTLNEK0aFn;Cc%9F9zGU`6DuAfIwM}%iJ0t^s!{oqT<-f6 zh>1o;E)Hj~a-yZ*`s)v4gcMLw=UQJBv{3E5d5HE#O4s@q|DA#n`Br2|H!kL*jzdKo zH_9?Bbhh<2){rPI8zwac-T73ToJb!}$%v0jlwEN)JMs7^-b%Xt*FC*QfC0by7QpX& zBG1^T9x1jYB(r14V-m9S1lbh#qZDbPR8b^k(R6!DJt3fwr!IChcgtuk6hN#Ut;gbq ztuGXpB?9PSf+fKyGy?S)but2hU{l(9n`{f1v$F}EY4Og2++~)5`Z1<#dJeOMb059; zMc3v500V0$%XVh6x9tsae}Nh_Qx#GMpf#HXAK$k|c9N`Ug6n3h=uN?Jq_@2%;s3#H z@6?5p8r$|fAS|68^WH!6^ex$Y+Vir`sF$>y5?ab!+{<-M?n^Dk6OLifZ@ycwtkg=Ch zh5*S|Kluvb8JF@*5RXW1g7&B=!#O?ChyiazFA-2L-?S&9(EWOxnRsm9+yOUfEHp z79BT6XK`knaUJ(TKu42+9dHGA)KO4JZ){X>fq+WB->JHtbb{~qe%~K&o+n*<-KtZk zPMtb+>J;l^^4n01m#eysrZ8U7d&&@Cm#n)`{2 zf>g%Rd8QL!Ijp2d$P?{$xil-^Kv*b9&Jw+bb}8a%0=wmcEp^0K#IPUxaJ~#>?E8w? zPi8gj%^*xtWNOZZQ>#p|T^6q9v2yR4;Z$O__>Yh(WqG$AlkTQ)Ecco&bgL&%pTKlr z1Fs_^Foa&0-goWKWz0gJZNCS{FHBvbhM^pUWkPHn_?LFTzc=_Si5L<}CFZFQ-P}0< zl72@=`m*tr0JSg;>NHF35Cu{K_@`+WFn5gwv;nv`(K`!HfS>Tz z7f=yoHU!()PqkO0A~irhnFQXtX4SOO;iW=lHX*0=FVA$zlZJ@OO24~W6t2|xIfiSQ zHy92CeUN6g%5tSo?RjiMBHF{(=8B0U-D3{~B!YGRnyzM#4BQ;CixIWkK+ccD1|XMgo<{#rii z3KxW|8`b3}Ay;`w#{^^hgQD!&y>$_xx_N}X{T$(4l(?wey3ZsFw~V4&pzha80M2?60emv{Q?h~fFctDNHcRyPJAZ2+_zU&VH;j1D6+>>-fD zhy6IfYib*!)rZ80lKmZ+OE68JQTAOuTIeD?sxju{Eb-ubc2~;xh~tZvP8A=@w$aiG zKIMuGPF{__RQW{-Cq#Sna&_zg1m~lu2$|Axq}#VNNh>!&8H-x9)yCvM=xnz}){WTn? zv_%zGG5;Y(7hcp+_?nKwLSr}Vm6B6;==~uRoU_K;@;@Nbj13U)r^(o7#g|SI_|6V|3NCUcb6kiNHCs?F`%sQ%o{2XoS0_4R@0dnDy0%TFC zah7Wb$g{lb>J;2CQ|MiHTmI>Q&@DC#C2)oc6mN=%tTZ9j>}9Nkw;ho@y~W6XMTnqa z^8m-ElaAM8^`+Zee}T-jz6zEduj;ByEW(0EU8Y_)JZlr!voNx^+^*kWFxl|g#L8bK z^Qupvrg&@wRks)1k-l>1pC70x$sZfeO&(iwooj6PvxVLvL0E=Qk*~SKJ$2%@W*n$f zi@izi0wO(Q(iEsBs$JmSfD~*PLU%PbJaR+|8)_s;c8Y(Z<9q=Q>o$BW%u*kNJGT<~ z4%j$f^IG`kiqGGAvh)S)MM_yRR=owfmqp|h=?jMB`X@O#Ny;=Oi^NL@<&{b$A;c5j zFLK(simO-B%zMeux|$!}5#vM!Hhzs-(E-Y_82GoF;O5{Hyl{r9WT1HesyCBYnDMJ} zHykI$gi-p|{5z7qhMtwhUxj%)n>Br}eYM;xfZ6h!AXAZj54UjHIS7dQiv$$A&{wA5 za!0=mZMQ#pbcJ<#S_^)!+SwXr?BPh~rbQMRRiO)pP(>~UW z{YM07Kqt3ktheq1VXb4y)~Ce<0Zr3dg04RAj{TkPgyPTUi$qC|80U*?-82FRKE?i~ zrxZ{@G)?RyvI(l~m_Q*mH$Sg3CeMS)OjIRW?O9wFR&329WZAb`?&aj7KxEw^3zaB^ zo@7o?(&@-hXIDAgw4P9j#gB>Otsetc$WHU4l%4WRfrT*r`Ws&ea828byesXyhu$X$ z#<2EFHo@nU1Z7>}U#%H<5VqrYSI>O z-Gc%+UQPGbjaErY`jOf@zIiEl9(()Se+0B@U!Z7sM3GQ6MYt%$c?SP2M2#CO;jkNmRB7$5T_tP_H({<^ZL=^@ zTu2zR!|sK=-3EI6+CmwjF;JWvIV;(N-zt&+Hj1_CkX3~e>hFrEQyH%otK`n@pV)6@ z^CX1!qzWq_H)FyaM+dSaJWd^ecuNUB+8FtQqYTTRE8f4ERt27|k=VCgU&*jaZ_kq^ zG8%Gtg^p0dcSs>?#w_+BS@ssxnM`Z7lN0(-nL6{`D)TBjkHY;pHDaE>4pE~b z6`zx9-?pA{#w! zk;q6z_LVKlj?CeGVwPk~&&PaY@Wjd%M6aFeicGb9liz{Yn>p7in-gnlYVGZQG0l!B zx9WW-lAb)?oG>_1&AlGO{R6F`w zGZ!$9&m0FBVuQ9hMNp62-dXG0y0Ea%faI~av#|I>s-DDj8LRf^5!qtQ=2!kKV*);R zQ_M3}#g{wr?nbxqYy7IDYD^(~yyf!{*Yt7k6w$}clWOE`=Jkv$BkZ~B4ZeEAc}!K` zM{oTPxp#ErULd*WN^awEa?5wm;TzO5LsI^^PIx5G>Ei#3_*;xq@yj3H;~j6Re}&re zcEX6v@8uenFfPZTxN#pfTUDHoc+MxrE}f!cZ&9&xYMa$D;Ky>>OOA5MajeSWI~zkR zM4cv4Kzh(Wys@81lya8fkp& zjSy>-Yq#nv&fW33n;XntV}`knAvaA@&`d-R`mb-bxolXW%4z zBXcC$c2c-!v~9q`o8{Sr9`a;OKMY~`#TrK7d=u>vGll_jVib2f3%dy0lD`mETk7|& zV+-!*MaZlw6Kn1jv4Vy5jT_IGlwfnLd0`W#zTt`*sLPD58t4ko``N}K&NQtv^Auwd zifg!qr0Mqj-#M)KYu(9pkXj=VW^seiV za5V)y$-$(l(rZBX?b%v^mb!Q%Lh-7~*ejK>gxH53WLBe;w8MVxWf3VSWC`WxHAM-f@In-h^*C8 zp+X16)+jV!`7ab{6ep+*jP1Im0~;R3K$KvEg72{=oaXY($dRDI1%eCnnT32oY7=aj z?_k3P?rki)F4xFe`ODpJCwrqgk5EA&M6~qk?abfs*$bm>tHL3Ehb)`K?}V$4=J&Lv zL-{=^d@R3X!#>a^GA7xVz!~9ee&>RZ$E_DcN*)#aUd^v$-;~Et2@ZPoAJPyU;)Y{q z7myJu{={3KV7=Gu`@R-;IJ=^{B@yh|4$^Zt!1FG`ult`6?eW^pGU{D;f-ajWq>U9Z zQHhI|{^m8h;;n0?ugG&uYpu{g#UwTo6mSzw#_OpTv2}$tzuE>dO2S%!j-%V!KZ4D1sdwldk*o32W=Utb5 zTCecu$-bHeL~&ffJ6LRtrjqM=VvZ^wPl^-0nJ;xYI`!5?XhSOb3YDxbhKRshtrbPv zy>*)QnIb<(CBLSUyEhXV$sp;4o=W!r1rntDzj0-iZ`y5+h6Z0 z{Fig42jQE!pADPiMD4ZjBubvDC~W!|(LgZ&`O4W>ohw^d zZ4!=Ng)eq`RGU1D!MC*5j@83bdPBv{E5>>M+z5?JF4b)CV(na{op}o6$+LB+N{1?R zXrgu|=*UxKGamk9@(Vc_vDOZx=OFKQFJ-w}PZy?IA-WU$p~Oy>*zTgguI~Uipc}vP zJTS&Zjck`8AB7qGoGM0C{{jwhUDJqg%Sn`DfwM#T;=bn?cI8pPFbB=)-83IQ#M1@JiJ?K zmQz-z_Uud@F5msEO&V-{qElP9c63LjZqcdP3?XfPsaxuUq#n*Lc7L?t-0AEtjd>`{ zI&4$gGdF2h2F(yh&sIOiM3h)OF~L^d4Rnv`1=3o446BfbhOD6>(MBxk>7nF|8NTfH~R%wA8l%e!U@ zPPFt|x(7)=L1mptsZN^z1KB!g{#OLJyZFy7Li45e$D|q&9glYMpy*+Zj+2gtu($4U z@{2UGQ0(64i5lY3t@3JyQ+S~I-ssha1cPlvd$K~oA~{hz6DW|PfyptZrNE(ETDrLf%8;RU zCh5`tl;{x>oklRaa$V8ga6zw$k_!4zFH}UA4+K2-0g9SpJX-~>u`Mz|w0K%9KKVmX z<@o|WbW$(~CxsOtIS&@474UD4=7LZPlh7hCnc=A%n3Bezb1$q1|fOFFfazW)XlJC-G9e1Sja=@dO1c z1TeygesVwOHL0-W`SAtF+iS$N7}w>pYmr>lo_WvFTc!Wrr0SeU5-i7LqAQwqW_DC| z2L;)NoliUL3sG+8fvbhw?cf7Xr1c5H8oUYon#Fdce%c$t^}R9stU=#d*^7iXUWz#?ePt1TACV zTXR#4?b#}2pi1eb2YLSAdH>XN7j*!a-h5pfm)=_pa6C~Ry}LOT{fR^yi-}f%u9ox( zgTo+0cZktLAyt#iE5MsD^Nle6{Dv@%+H5g&1dt3uXlK^+ki22-drK>@s; zf-H`I!a4yg7rP_gn(Nw7hsr zSdL3?KQ9IE&kwk}T$24&U~b@7fx`|q7tu~R*n|@M)t)rR&%Iz|FS6=QT6KRmZ$es^Ww+icv?!LmU)wA?zDh>ek@1h@!{&sCipZ{P z7HMyXRP^iCUS!}=6m5RiyDlmZV!OAMN;8-B4b&C|tDl)#uv zaR?SCT-Moh^)bPrQyHeoa{&EZZ4S9s%gzuYRN1>oY!&V(wX3_jvs!&j>aCYK(^<8W zIw0_#VB9keDJh0`MeoR(d>BP>KEH}D{9JNZL7r;NENRT|`I-p-h%xPns<87(tLkUU zu0{Sj)Wi@-9*Y2Zh!iklDMN32T1191l;@SC02P?c96UpZ8bV0dA- z(aSx%`{)fom2FsgjLA*?JYiPkY8AObYFu`m)ltAXzCN;h0eAA8Xz2}dEFK<{_LA4I z6jC0|#YHnyiH!(ANLKbrtxr5^M%7qM+7bn8U$sHHJl3rqo64Eq|o@=@v`ra+N z%OmKexOw8rl?A_+HuIr52s|}Fb4|7{zvyvjWPcwh4Oz^y-XzG(tFf@K%Fjr1F*W_lu}acqz2x)+sUD^N0QW? zb4Ph`Wwf;VSwRz&1v_Zhm=u}}heo~fu%cam!b+%mg~{@L0$#*}x!Eye2fpM9WV&Bx zijhe;*w3(xX88nvbiT*&S;j;BvX)I!YuTTq=mdKE0-gvjV(WXu2=llo96j(;WQb*4 zLsGmdXJ4~Bk{$CcmJ%$4y<)}%c>mve91j0{Z+{gnjnG6_7=d?3Z%<71_BcB75lVcJ z;>UP%!Oo)>(Z*p7YKcgWAhk)S{*_eeX8Skk8G0sE#$l<`&>4s=^&tM8p)##<=s`KF zTVuFjqu{rtZkN^W%Fe^C-2wUa-rnS0x7CepIF^vc$cIToWfT@Y-sdeTV`s0ErE`;M z&8DW;!z0xUv((eP!=;K+`3d(zz--3)ZxSi0esTAMq@l957zOY{^4DS<>-fy-Lns5k zj)8v7`zv|)4!h4E*+iB8lu7n*2;O23KIVjPR%*K3x!298?jSnacJWG-V|)c2Y1fjL z91v~uuM{3+dPjUU@fZlgRL~&3d6;5lZJJ`u!j^{+Pf#ORoLGfsK=6k)6t86~0_e~S z0djB-bN&E-Xj~#KaUfvsQ=QL_oEo&s=XlrMAnSR#dSg_U^niP7p!R@T+>Z#-4ilD~DKf@(m8FcY%Ex%WRPy}yYqmP{_L93D zm7C;=wTA$Nx*l8O4ep68g$FUL48RZrHiK*N3FCoGJl*`s`n+6FX7dfSqJa0bUWyvu zK8=`$0r|ar=2rFX7WuSSn;)r@=sxJFDp@J?X)O`|7P6GQYGa<%52-Eb>{Wl~ZKS>S zi4qBF4@?es;+pIub)4x9Zc2KQaj+#o=CP3KlLZOtikoixwcm3ri(27c8=g=gPD>8h zM^kdW`YexbV&0K%NS2Lc<|E$vB|<34x0eXr@7m4MSLZ0GV9+(6v(eD2UsT@ky%5+6AUU8_Rx~XPGR$i}&kqBnxx%64iNR6q3 z1SK!0_9P;2G^i?CUZA_}`mUac^fcP;urs9vp!Pjd;B*3VQYGz8F%jzJQ4Qx&)Q}p2 zbRdg@X}5R<;U8&Bw|E6ZZY_0>R{-SDvwOT6j;FfBr;Kmv$NE6pRg={sWE36B0a=uE z1|^s-)9E9WaT!n)Z}m*t)v53(l&OWij2dJ!W`5osJ7)}B8(w+3?T@)&XQ8tC97CR5^W|yq=iZlWsa0@xnvorex zWKZ-QLuQDrvgBd5{+^7m(x}0Tm}A`&1S6Or_{(1r#jTuR8Mou%3sne5F(#?ZN{~Bw zGP2Yvp6j-UxVLR@gGBSKSp^4+U-M3r?pGyqrBcBqTx|sg;qA#<#hxgow5cv`5Gaz5 z>s#Rc3gnDkPbr>!#N!3pE5|B~Tap=0hNM$EzdLKFrfeFBMc z*M!RWW$wV!*kQ}mHbbw*bWg1fsv@&=_z1_cY5gJoFCYFk^%8 zI^b1ZWd~k4Jo?}bFfx*K#bHUgQ1STS$ts?U$;m#1PDzCTRw0+}c?0M%8GLZA+;zUR zN5(?tWb4cPniD7(IYB|zTQ8@&l5dRU6a5Vlnkxm})<2U3HV$PISA~$L&>N2vL*EL) zW2Z#xHAHo0Se%|geY*7=onaO=1h4C>$FnDu1l^JR&Vr&a>9lBAst#}6QF#3gPpR?r z*X_G{ezf%UKWDfyS5`_Zmy^nL*Z8CSmeKYIoVuJtM9xPB__2f#YmHEQP}yI?{)Kq_ zF1AHBh>PsLAHeC1cd+aHO>`^Oy_$94=^ zGCx}S;h(^saPQ8z+>VSWe{XUp`^yzLDB&VOj9~B5Wt71>jvzii?vn+_ zGf2yjD99$1a-rPc5h5zz%w&l9KZXCIWQn3+Y7yvi+!7EW;jGq5cHa*0I5{rMz|L}y zjr85sT0}2?R?Q>**riaB@wddrEY~}^y%u&4x9jgyTTuEcd*D}Z5}?61=>q$=o6%Nx&5GXbW5Ycb zwq*4lOMP7HU`-oEPQ@`w;GFLe;lTIruwP&kE$($vk#(xHw*Dg8XKHqelh~0(y;3)M zTVs3DTE)$nj#hpB54|yt8E>-cJB>S8H;#O&9F4>H0(xyVD@Ho)8!7GcZN)@{v>_Ik z&g`(W(CKb5)=*ImaMO*pJbxpXy$iK?$h9vOxsXiu%PJDRJI_3Veub+@=C70jkEIF- z0uwo23f$dMU^KyO^+Yyl0V%L5RX`S5BD18xCAvVbTF-$Wb6l=GQuJ0Ywf#IBUOkU{ zQeQsET;>f|%5IPa*M9r&0vO|M;^h*%#HC+iANzlHiJitQk;X*e)r_;%=Z*Q=nU2%q zA4OAKNwj`_#S(29r{P%UsMr)9s+A4yFrE8&ap)~CjkOrP2*{sedKKd=R>ouShrS~# zQqw78r&}2lP1D~jv~S|_#0I%`WpBDyK=OisL&AWgx7v>`cQ4%7-t+$sAeLu7Ml0+! z>hmod$I=a%LP^=6%Y^}1EoexHw{}GaV|gB+v-ZaKG-m6~@jbWD2Z{}6DKkewmum^aW`T?3HfRT;5cg?WjV4!sY2 z;59bO^T7e;i5o)zv8W^^$_iWyTcWc5xN2Y&k<0mNPX+uZ{NYeL2{)5)sJ$h68?Mkp zvW&8P_pv>*?-&g1;!}^&Z-;D^P)jshDgs-~*pmWVSq>6^6<{aJsDo&$8D}J59Q4|b4CS16XQ){%=KsH z&8#XftC&Q@ODdwGEHJC$%xQeuXqE`e_wdOgAn^j=BoAkKZ8N&74H*%imqE$HFjvt0Z9Be+oS&_T;Tih8k; zVH6^0QlM!uMt6Eo>hzxIcoRl8K6itWi9-{XQl|)o<*|frycG;ChTBB>JKAo^Z;!E@ZRNs_NThBn8IyYYwO##1;d89h@sI@zYX zX%`+gT&U|dMq2m3N41ueJ59^C?3A^#R!(R-IL_wFlNCPRntwoJ}-+=1c-<16z7Kr*$G1`IimUs&V+^H+hN z+z>RR7P(^UnGQ_t3*S@<@W3_#Pq5LatKMe|63-hp#6yegmGJs$@FYwCRQ?X5+bWOW+W4gBmxGE|YlyUthheGYeP&#ecwnWqT7ST|fg+mGuV&Sx z(P2I^*h5~q8L(LJ)P#vs`eZibP-lrSmeH7$t zce&s>W6ezmvvambde^LC)5Prow^Y-svzlwAn%k%*(DdJIcE(=@bH4OG{X)FIuH<8> zq^T`Crx`JfJUWFW#{1~D*gMhxp5cA;r$F(KVTN{Nxo#HEH_@^=Qe{9UUvyJ0q-NZA zklx}=-g*%ei|+qou@Ywno)z3ypzzlB!`}(wX&sO-7@F4a1d&$uM<+v21e|i;%rJUy z63yRsU#atPzI>W|xrI?_Z6Ya9`=h@%OU*bbm9bF17cEV&oulYqRMCviq8=%FqZD1O zi`ERF=twD==@hl3=*mmleJ=$&M&TQzTJ~&;C9%cv<9xVzUB>N{Ia`%EwcU4TXPNh; z%up$FTxXX*AyQ-25pOXV3N|8%ULtdFnHMqKb~>bfWUe`5Gya(sn!wEg#CI|hO71`IiB;bSxUQ@iO{$}fo)d55^}d< zg<55`Tgayj(aARLO1dJ?kZ;P8S4+MOxOeMf?Eb_s^FuXs=LMFIHH+Zy6c+I`bLE78 zWb=XaCZ5*O#I4dxwRWoaTg4hE-EpieQ+YOi>hzUhb-gJpX7WR;^|nax{ODA zcmz0$>Xlr{o=XH|M!sjp&&;+%gz?CGQ~}AmzKBX;y!l37X)G_&(?2e_h&ZYc>z{hn zRCkK{pkAo28D&7iQ+Lrq8GOFurcs8ho4fxYiK_ z1u^6@ZRF_`OYk&wr?ma%o8>UcGG6>+maEv4cGwd40I4>1Leo)pw2x|QA;foN41>G}S)>zva(fG_>)1b%mRy2D%vT78{w+rudeCVTFomGN59?%Mdu%P+sY z|Kp}Fdd5%rUDIbq!K<9rpZ*LffCYW+!~2l_(4rhhRt%LsR8pfDDk=Uj@-lB`0~~fK zx4j_Ch4jX6`TWGet@eqR%R!)b_46`Is$YTn&)6r7ch(lg_T5i35_)f)$e%Y*4VaA$ z$I`!*icPij+w(V&fePoUQ@%b&gyLDCO6h9$M-!0&UZ30aS$1Wt2@W<))MW1MG~@=F zcIF0i62#5xh|}Tegv(IGZ^&)>cwo-k-a46z$*HPt&)wh!-3*Hee0#g4JZY6KbxSJmJuiCkM(Y;Cpg;zN985TE#37Fg(3 z5#M0}_KcD59Km<~xABE38Y1I+*SWw| zr^?t*GJcOxfz)}VMoXjA07}keL~BY<+AnMPW#;;_;OH4=R_5`}w{aA4=K5bRtggGb6vrTV8Z35r&Sq@bb&UdDT}E@uOh<`im|IU$h|a!mEk+Rz>7pcImH| zUcPWC5nm9IFp~V@Tq0jh7z@OqGxG#z;=b7gz3beC$jt*7CT@a>I6CamDCm|c$^M4I>8eN zvYi=&GaGj-?t?r(_K+mUpUp1>mK*$FGgr%6U&5^^T_b&rEb9W&6$%__AYG~=6iAmY zxd8ZFq9U%W&Qm}o;sO;>eWeB}5pz_;g;#5!5^*LG$X}+3vov8;-~!aXOn#hfai9$u zQVMNAV2C=Ws}O6={)~!Pqr4nn==sC}?Wc61W z&b_0%LC#mArBB?>AgC4jx}wyI{OpUeBL9IDcfz<%DoGeuY3B;%!JwX1bme!C&&o=np+ta$Ahtu8%E5KvvO6$M&)bkVouU}o;-l+;0N3PQG_frd(1sqh|+*KIbn_2cL{yEEjMm5kraT<*T2%_skxvqEAM&jX|t4+*v|>SR;k5!0Bo=CjW!d00F5Yv&&A{7E}^Xy+#Ftj3`& z;?T~O+F6D}%YK7XlQ0(R$OSm$oUcQ3v@=UPA?=trWbxxLqe^t-DcUL2&Is)k;V>5@ z@;R{c%iXdHe62V;uyo@o*im~{&4>OnA9|}z)Gqt!glx)NWI~v+Upn&veZMQa&xeG} zhdi*GJ`Kd7GyQd_4-W9n)uC)0=1P_h{rHtibLmhUA&P&eo!#QVEWC;9Y&sXCX7y|e zkC{zFXtilw2i9Ux=UJIZXE2eT#1o$+T;)~$<=wKlFPD6JtNi&3Ze|fjmj~<*pY1Y> z4%pXRp=J?A4V4kFOtN;FK^!#g-~In{>NHH1WmslT!&EhK8ZsMZwnqaem4$&)bE=9 zYs9nu2|Prs^Togh-#(CCBs{qdzTJ3{(=-^L;&E~{81E7Y#eLgx0tz_-8>F~{C*wzr z6QhPgcLLALK$Hv9X58~dj%$i_%DYO?xBxQJ-Hf5Pej2fXcp0P8Q_+E-x2!S1Ga_8T zj1Qi{*SzaiZ0ghWIWH#;o+uxO`6m2vZq7S2XPgt$+$ZN9q***<=vxe0z=Xfpr|GkS zIs5T#5_TM+*4$0-^`F4ke@Gp{=G>eF&WpKCI|t?@0({=P<`mi@7YGJo2vl4y8YaZe zh7FJHAwITAoZMKGIJ^|m2Lq)G^0U|~V%0P|_DV%{V<mBhHz7EC~oFWOayP}E9?8w z^eGXn?VBMnx#G>u4Y1tM?Ci)5L5p^9wIa)+5y>m*-Hdj`j<^#KLBVsH@%74iQNlmb z+t~J;rtoR8ccq^V6A$2NIL8n%!>%-CN7+ z8-w06w^qm^$W6|Q=nr7yks^sJ_-`N{$Skkl8uXf50!;@pCdPvvImGGTiJx6ade9pE zL0oWCT^&l>T$cQ<>2o7~iqb+*Aa5`Zf<$=RavO}V952ZEG2ZwnmCxTW`VB(BsSzjL z8k|N{B_awa`jG6S!PdlyM-%?GjPO^y3+q-=+CYXtYa6`r$s+&cxmekjZ379y(|BI$ zcTGEcit>w^Fw;>}kl8Z*kW7`pPeF9!RB}V$NAAD`_U{IGcog&k?$_m0s|+9*$qc%` z4MY#H9lC;v3_TL-9E?SLRtf=;#f;B7jT?Qa-=J3h26sP+bYw2NO3m>$P@ zFj(+)yet#`$9%Wxhm3gEkWK`0q(%+>ny@xB-MvY)5wjcI;%(^Hz+kE9hUpmuby~nJ zZf*ku3yqtexru)#CBsQ^OG-agLC;j7hCq*0xrTtDi#ANp+QbHm|A1Rr_2`$gt$Uy= z8euCtXIlpnk#~BA#xlx-Ws)Ml{S=N#au+1*Rys{!)`c(1!_-vlopK}}mZ$7><^aZm z>VF3nX4iFTlCV|tE+K1CR-oy#-b#4-`~5$8AKep(eaEYa-kT1R%99kV^ly&NbXnua zP^kFZ*dFh+O=OJmTuqalvM9BKV@R`4<)Kt~OvHwhss~zc<16;Hm$28~#QTx71Vwu`*8Cyo=)C}*L5^O8CSFV{?7vWt zw3jE?v=p?Bq5-aoof$2iwHrOtz+ zsI!tfq04#HuUGuSD$0L9p-v_kLHTSZRcilB^@49m{{N8o1wfiQF zb`Hzw?5W;$FSYv$(qi)c-sfJk#j0Vf#rxb#c0RtQ&$D6&qdUDh&Cc~NzzfXIW1}=X zFiRQdQC9Pz2OWm!4nOeMdU(nvnHbk73dQeLsF_?UFEbRC3=e0OoEAAr_Kw14G6u&r zphWtx%0y2#WuhmC#tnJR_%gR>Y#O&l*+n8^9D)DOjvq}%v6&<)D(s$LeT<{ykxX~~wjtn2Jsyy* z=b5L+H56+O#Vh8t1m@)N7m8o;tCqm8#Oqwh;7L+_aFuqqV_H0`Kryiu(YCCRck&CN z*jD?P6(&pdFZnp&8JvLSD2+wbDuOvfF@$d<-l8^mYICA4& z90=R@BiQml7_Ors@EYDak5v0J_JcJ_mVN+(%ATkOP)_iyC$M69wDd`dTzR?PRaGBg zR$PhwbZ$8QnFQK+zjC#k3t>;L*xb^QSxx_JA$46BM6R4U)ut%T!Z#*k?*m zUDXN?tB5s8PKHC}{81%w4|9iIfj1_vse{U5KSvQ1XEN9Bu2KWYeBMsQgQ?g)aG}&H zn#kTdIa?O1@?iGg58+Q%ZIHFbK6I_4iMPZw<4f{%H=TL-7j7WS&J`5Q6c>$XE{7>$ zP^=8obtq?tViO2=uXc=s+mhv*y!7l2;E$ zr{&H0%BIj;p;!Xa;LIjjIq9-2S@twGCegN%$XOz5B~tdL0CGCE4ec7*PCq6{QhrC$ zI7zxlCpmy~@Z-nBAS0W#6g;z+hSrd$kVCA<)adS_c63v_AG?9S9NVYoD(J z=Mu=ZXXpTyR#6uy*MWJ?n3Sc)#4!ywM4cJX1ayGw8ja|m__4~ z{Zb84WaLUwkrHeZG-;#WeY_q(n=&vv52tvB0HJS0F}<$>%2=bkX;SE9ax<1>f<<)M ztznhvfsRZ?f^f4h`x%7$HxlTs2zS-6ZV30qFc9vCe;tl+7anjBu6A?Ug$KI``+^e+#mNLT=$yOFn@U z?1>&bQ=<^S=%t<~K}|j(4OPH%m%U;h9Tk3Jhy59#g+M1!7!g*$A=}?kNMk}8;qEF% z&g1$6H~BP%vh)guc64b8T~Z3qPM4O-q*pgYIjM6rr(;W&)1!Gj_N2d*#_CCrqK&BHHKG$uh^8CWho+G{`fE9ucUlKc|iqC*!dc}BGK(o1EeCvdHU`*7Se z*RuPY<=SA14CM_B@6<_o>dI78XplW>K_YNzVs4Y=gVMXDCw&Ul>2^Zx1d1N&onK4@`G4xj0`h%WqewdMMc=ak)6p>YolWSSU z%sfOgtWBc*l4pN_7PYKd^UYW};0VeSVdZKSTY`susk%T|jujrQ8CjC&tryjsByWdj z*Br2*74J`VIrJyb5=q|^nkrNEX^`U7S`qeUbn1nF-ud!L^z-+ znG$`M)8O-oZk;V@kQ<(wa?2P@ujFS=E3awF)hQYHPPBm8ads3?07xeU{k4j2)5Xtp zX^ae}_nHDcAy!b>&Xn>{Kq2rpV-uId*pE?*Ta(?O?(@jybs>gTd4ly1oGTq;0`S*{cpq#2GG|_%wuJ0@&3ESI zcZ1SMu*}Pg2Qw#@KbAHx)w2+swM|P)!ul&)>mjfj_)+JH6*PUQW;FD6bg!_N!a0>bq>a&_4krQwJgKh;6sU z_M)cs-a3c|t!xUcKZbfW$Bcc9cOj<dyH4|$(&+}KCD6!-jD zlB%R_!fl^U{)OsonW3uW_&Sm~jIG5f_dd^aXkWXh#`~{g!?8@IWZkUQZ}eG-unvA% z-D{{^)Oz#EV;{lV!XgiXIDO{S)i=0$4WYq~9Wm)%5L-f8)Ko=DDj*Q6rh%J$kHeB~5MUKlOy#~v|bWl6V{b@^goWrZ^^)AfgZy|kWubZTjJ z6p-#cnkoFRhedIm7q6H*9Url;;R|ZzPL7A-ot#`NU0ysJnWb)QFTf*F=MW|8Y_8cK>TJw-d1C`Ou6o+=up3U%R%G19iQZTHP zcmyjzK>Dd6khXzDIyVCQo@Syfk2rp!CY#))O3%OW8Y$}*NO zMMPyS8nIy!z_;IDEH@k)<({8}(=V%C{C{{^&G_a@45!8XOG2@F8SH3j9;Cv(axw>< zy%BIbDxIY-0}$l#3y#Wk?aOrYTto4-{liBF_h! zb<~M%)?;ZX?Ys2XAhTKnomP&nCtVr;$U<6(NWq!_=y=oYEg%}QuFVRlBli{w3De$c zKRYACMV4G|{i8(U%L;SBn9W_QT|iQDSoCvlbbBrvkm;)Q24AUE7XaoHGA%*ji+I$I z(;&%NNvi+;1P z#os^dauj7+?K@$|SQ?L2wr@HX_5gxu4p%dh$D>=Rj^Q^IP8fgb+X(7z>;njs*?J3~ z%*1dVHeuii{4!OXXF$JT8JS0Bj)0G%ra(K-R75uyb)DpAcR9~-#)&s6{Q1#@j*s?GGo*NZ=yzrXKFuxOGc-CN<8ZfH9FDKr-H?vz1W-(L?}dfw{SsOWrY zhg$?#-y=l@E>hli1KI7{?uSTp)*XoLs1#dJ0<;230kqdC5@_d>vF3oaT6)T*tNJ4V zeCx|}BZYBm@tfAAS^CMj8we@h?SMUn7HT|$vI$$>bY>@>8BUX#RJ5q=$P}LabRS0? zA}f*tH533XiUY;#)BiVdPO{|zn0m@ zKC(te|I5U^ULl2&WAvlj7zys9^s5^M7FHP(TC}a7AY^5N7tGyeVKXk9226)&GptjS1( zedl$g%qL}E&up&Q>k-;ze+c^`6>g@AD#Ga|+aft;{E9$*<0_Bb7^x+uwL!iR_ubZyw+o0v)CT=APA8i% z)x9L{9O9DIAcp49*xSnhpWR=SxYYxrx#YHI(&Qw&Bu|DayEU9^`-P9=dg?2hqwc^O z7e~6@HFMaaC3|WH!%&VAPIo0Fo%^+yQl^MT&{W41Q6o=u6l{Zw5@xoAi&&|xZjb*V z&)>*njh;n=xDd=~f-o{0`X~^TzLnciZwIE9OJv_NWHzhmlPqlZC_vhg`gq$@KhI6n2r*qpUFnP_Y ziSjy7rmiI`T$Xz2TE zn{Wb|OS|Tg$}W@SY@K}e;mM-wl`8E&JQ+=bjs}c6JUKV5^f8Ag@0I2|-|lgE^6RQq zzVG@9ta;{%VQ|37%i?uUkqQ)Q!e^ug_HC8%!<4zy4hYEll7iB6UGo%e4`06j1tBKk z38^d#x@K{z2~XaTtJ+iA&^4VZ^y9+RLyqu})LdTDna?r0d>eY^jVYe5lVP^T|9PuS z+60sK@4C;!JuJ5m@SLXFQ46&k$7F(*4J1K;5l>Hp1i_AXl`zJ2{mtWa{R!Ks-v}ay7WJtw2#u$WU^82E+=hM zdRj=Q-Qc9%m!4Lrznaa{rE%ZoPLBHY9DT_lv(G0G8nDx84`rr?uJ#CIo z``Af)GCi$Cr)_uA?o3b1(`n64+O_Fv>f&PD_qdaGetMd%`|w96tt>rFm^9#2qu{jK z$uTrN$Ah}`GAGSQPrHe<{RuUj*w*@vb@J@O+_VFU$Zv?B?8I+Pi$6;#=TKOtPzC3{ z%mN4(_@aVlE}ePtX4c$$ysQ zx1JFyk)C$5q@6~Zjts@i;P-toiOp9fqEf<)FaK3~spXQM)g^sSdio+s|MYJ8qAKrw zez~dB_2ui?lD=8ev3X*jw}bVkB|Uwjq~EK`Ka<_9#ivSIgD!i7J>;;mCrbJv{cWV8 zTF7?0V1u>Z0qA2ry8<**ii}Sy5;u>rUwF5BtM=rOt#4X3Ykl{|eJELycS*-B_3>!a z5%#rG)23AaM3k{rvTY{Y%}`l6Gg0GSyE~6X#*Dob+ZH|O@m}99h#?!ChskbM_qoAz zzu3;!3NaX5pk(F!Dj9GjQGq}5&D8Guqt4wouR4e1R$@%^@C4a2spN{{E#B3C5x9uF zr=noXlz5*^+g+xZUbO>HHdw=$_I8MADbwxP*`3?1z@>; z>L(dgt^K*;RcXcRI*a$~Qd}!miTsA%3Yhdur`FM8V~A&iQ5SNXEwut^`2*+!@!I!F zeU>GoZLd{z@33#i)zD7qjGg+D@Yh=r`Gi$|&)|-TYKe%J-hI9-xIOG1JF$u(>!B`6 z89AO|H#s$%ah!%-^rVMSKtg^(I&T-{oLa})r+QT#iLKHt2<4Yl%9`0o+trvvOa09` zuA{v-J*`$s&v}IGU!DybG^_O5gDxeFUaONOvptaHQYDQ>BIg+`QAU>%E|E&}EE)st z879d)&cg3~ep@_i24)yU9f+h@dy=>#dCHdV}td3rGZ(}698?HQz7dSO;$X&P?v4eAjH0L z&*c)DEFrl?WOuyMe?2Fz5Z9U2_aqF_gW8`SXtQfZf0W9*O$l=s+K+D&PKZHep^2v zAD^0KKSmM~ax5F)(-n_i9{y5x{)fKYxl^M3hf?mB1-rk(DLOhJFLrA#BR$M ziV-Wv9MBGXo+@0yy+|n?uymr{+9uwh{J$t4ieH)~JIj;FEV6ORouF$2UK3~}M4_^p z>-*>i*qhFf)1_8BOJ`1ACE54Sl0JzjqL1p+TeV!m6@-b5mb~wh%wVn+s5f%5$SGcB zmffslf18ReN{*Dw59#RJQ_(u}?K*Z2lwcsiTVJOJ+cWcd#U)&< z=xwKj$NmL}IJ}45%3b=OH5QrLL91E+ikkBxEpz64=s9Gp-kZh>S6KQu0p{y46?j+& zd<3xhGL2A8=_DQJCN4guz2?B_-s`^yeQHX_s)Rb5fNbNq&kX2{V~OQS)#w*)kQ_}l zl;z-0T;4jlyDs;0zidS;Me$$g;@9Y6#}fkFLvWOE<`T)XJd1J{O9pC@vY)ojCxkwZ zUTC$R^*=HhqBoR2*q>yUqe%GXzg2D9#Q`Jl2Xa`hNf1;D@4k2OVZ%l#AN4p_i0&}3 z`+j2WQ8ScQ(iFmXNqCSBrVG<-B}Z_kvuZVu9pdqS&n1u4_|{fu#=}MR7j51&Is)c z)y^@Zj-tKZn$iZ^2!4b_aPvjS9<4*MrgrxT5CZ|c)Qv_hwSadV?W|;mp|3U2{wo=T@Rb*~ip;(CkUH$C)VW3HY_B~- z=N(}$BQH{_T&cyh7j=}B@GN_dPF<`+Q&Oo#$#bKn&zV9eCRA2`IWYp;oM~_>|A$lCQ9czwefd%3!qfqu1CI=}JY2B(y3zPf(D_Zjt zJM5&S1od4HQ!qhCNwK4fEiv?*t=2oEBz9f+f#rQgsOC2JGH?+ED`phNPJ=O%`xM3`88yb0BS~>tAO;dbUU$Ca^^li z5PL<=+!x86xk4feIe1sR$CZ^1@3DswMmw)M>-vzpt$nAE!?Q?BJA2=2e=%A>dB85! z33OX)_sA+&Wewl>lq|8s(&?v=%)RL9E zjjiw*V&h8j8%JXQxiG_fTVq`#^o#>AZ@B`%?Czgz9i;(KLc+#eYK*pdSM`z>oJ0)Q zQaENv2TGCc)@|_CHIUo+9)_#s!=P13a)rRI$WBhBc$58*07-$S5nUq89%1y+eod#X z>5^8IoTy7au2b*slIoPaU8k+;l2(+=7Q~WKQtS{44Q6rDTmPj}0smSQ8RnzflfMWL zXtLM7V?25&Vr|}A-$Mp7expV%X{T+#4~pJZ4b{QW%oTlxaqu%^y6`i%<=2v~SQ@%C z!tM>xNnv;E<0Pf%LW-4jX}}IR#Y*n(1oYA>1V~bBRg8ItlUIYfmAjbtc@H(ti-DnxeRYd2(Ac{b;myH+&5k z{%Y)vh0xvKpqCh)(_*~8KF8Hpfy`Jtg`UNICs#5+d52(r3WfRk4pQ<6DH~*n_0`Z| z?8P@e4%RnEGFc7OO`^ua=z+eG&v^j7`l%eA1ZXkN#+APo<7&R3eQRV<7kX!TModSa zfF0%_`_d28vMH9Hj+Dr_XE#Dp`=iyE%rP#K68ur0J_lJMt3=gfXB{(ce+Bm$zo;JM^O;S&4Ry&gWkLU=pc9%!fn<0Og~311A2 z^i3j9Mf70i%KuPc*ZYcra+iaQ!z)x*o809Mfp)km6`3nLN*wFLj0a~*MQL_g6f2T-mSo-{!!;oEQysWQy zSRl$gYRSNZGN)Qn%GBv-Au+suBa3>x>M0s6WV=0eWz!V zKG2NH_v;!E`9QGOoeZwu1}?}p=I^R0Tmi{Ul=-+aluN9B=Pcdqy@^6G_cOdtugL$+ z!q~L~HUL53U-~f9Gj%Lcv>o|0_we8+?3sLmN1uLV;Y^OUe4pZSUVdgX@3G;vjCc6O z7Ie(_DjwKE%O~+X?$eBk`Hh0v>O7ZGmZiFWKJ!+l{Jlh2#$99u{4fWIm-@dTSj~6W z5^I;K?;1BsWq@9UEJZ>=BFMAlNgJ8CT_$$gWmdd10~oSt`hxrH&2DvzIwu&f>3oo@ zXL)Q>pedOXKj(+o7A2Go?Elti_hK{4?o+^RiBw#?Y8A~ko15Bt2E30p%C$(tkAjWm z_ZK(IgEGyP)$dn$AKg@0{kkesvHu&csk_>LAK2d}CC*E!T_WNWOo!IIWe?;U3fKFw zK?oME92HB7wKI|IsT^pp?6t3%FC<{Ozcwk6TBB;!e|Rh^iTta-65IvY7L&6|=NugH zKK*6@qDNHYGG~XG?6Y1D=u?-0x@bdnqlE7Qd?=yZU4#@C+6j>)u`3n^>mH6aqopHD z!_g(yr#!9_6|ECPiSIkjSlxAsMGEf<3c!H*ftkF-Bci*If9=szc9kkTm zP?@8tCu|wVPs428YB><_{uXU?fb<`1im8212kHjn7Yq$3H8;pTHu_z9Az%vqO3kG} z`&+WFmhZ7{AOq0DssKb2fxBKZz zC6rb{uJs#34*EZhgzB|<3~#VaOeV(OGYgu{_>|)Uu>@6wVkFwDg_XI>O z`f-7H*7NR0p#YdibgQN5a1|pc=Kj|amipSy$BN=^x9Vx@iHse(f1{$Mk4^wCD~~s= z=SBb~?*Cw-qKkCoeEIZZbn0oE&{6w5funkF4NW)WMp2oetDXI7D!d^cS>Me|ilSs<{)rw#pUuYMYiLeV!1nnSYES%GG+q#^qqG0ioJ; zOwg(kj0ui3KAnoqho+qwyaB3lhVMUne#iH3K}n`-*H_5*FX{Ncm+z+@gP2W^QaH?_ zGBbEY-)-2TbLR!p-{mO5 z^TPeYoO$5%9uV?LC;19KrMeZemJh5H`!(-_PfSfQo>OjJZ}LT+9??%6`$Y-Q@-u~* z`mag8;`K#j;ECg-9q<1`+`GU>SzLeI*(3`Af*X|}Dr(edK~UqJ2xyipu#rHcAkvB! z#Ui$-sEJ}#OkBV`eO$$Qsjar!*0#2_*0vUest~{g?|7+#wid0`XI-qKEf+83{r=88 zyGaoH_y2xA@BiHoWX{YpmosN(&di)SbA}x~Mn9?C`y%K{!Ax-utECuk}R(= zH;T8MU6QP>9fH=Bd;04`;yi#xOVU53o2}wC|E7trzb@Cs*i=y4nR6#Dd@HtqTl`@B z_UEqKq%Sy7yoIl3m@v;{@~nk(4z*kLxi%2I2aKb<%q3Geiu6gNhZ-Kq7e)h=d|oJo;Vo9oU_d=yVeya*>)MPkQJhQt<{agUIl48iF3h)Cva zc*L&mLcvUSgPC9w!|{^i^Sor{2~cDH!FB#p$4l;#aV8U?pnJBFa`#lZ70u%2dM!nC zME^aOF~Bd(?&l5Gl~J-ZMs_-PIOy3IQotw`FdZ=*{^1k-lhu(Ip2Udd{`|piq8sO& zlWo9eM>yY6&N<8l!bGFjtaCh}tD*k!AqY*S&K8#i#(FU7vB1dRhlj^KqqiRWZ+(zl zSg`M%@S}Gg$AdGfMQ~)uCr^XQsnJ^qkIcuW_3_xX5S5|tohobPe53EaR37>!V~O$y zi7HVTY;Ip=^ubrS{hg>Bi2;r1cv-jngtlmVyRc=)$;`*Gn>uNZWi^09HwVMtM-11V z*`!|Q@AqSDB0#FG++Fi(M*+PDfOFmG>bFqqaBuq`%k68XVK;7ram~1Qx!| z8D(SCYrWGa(k^o=STgF9zC#gP{-KvlghzP(@3=SBNj~QVUl z1fOleebAZCd0A@o_l}|Y8~63gPoczN#=V#9W_Q;l z!YYLM43>9IV&eGIaOaJgGOoU}d=vIJw5DPO#g0ZNxU-qMu9NSr%KZ;qeLl8{IkxZ^^;B+c zFV|!j%stV3eeG?c;9TdU3qZqQit`<`{17Stzce3 z@GJq?(Pwc`zVv-pr>i-CNZQ3xwupZJm8_G4XWn4!v69~qe9GkqTd&LWAj>i{AmjJ{ zfuj9_Xpr_j&Y!WNUR0J_!E6XV=17g09Ci1EM}jrh5vd&KKz%fPi(lC7$eJ90?LC*O9l%iO$N_6}0Jb`{SGfwQ$*|}p{B7IVuPx{kZ(UG{ z=~5(8QnPU zt5hjxcPJhG8p91Oi*-6$?N;1VZlVDdf4`*2wK@5#gNN<@#$#pFqt_n^w;+tlMG z>=AtWCrrF-?B_k+_Ff+X_6f2I2>PVwMU=zE`5fMR8dY*3~G77gZGWxbJGV0c}J|NZrnP+C06)msL_QkBN9vCGS4rsP}iPZhNj2@6_exKkyT!Kluwc9u#s$ZAE7@N zu+@D8hu0g{(BGpS3yPcZX&KFK>I864dRG_EWBGpT3U z+&GhoUg{i@8eK|QWPUYQJSsF9rjNTPLnQigUoMOMOtANQXZevEX?5FdY;arDTi%7z zf8-esUh9aA-Fr*C>8}=Ilr5N!)hE+{L%_C!f)iS! z)TOU2rpA@Y*zcmRLL|g=Y{}0fD#=RRpg7q1`Z`~^d^2CKk9^Wz)X-iVnK%}!H-+%X zVl6BV<128OaPwm6$LtoEDB)d(EW36PAnjFK5xPUnU171lA!FfJRV5$0wDE$* z3ogFs8>AUQ+?or|nX5EOi<_R%uv|d!;P0&DDJtt;2Txm0Kjob`d>n}k;c=K5be;tF zeXGVswzwn9^!=61L}Cen_&Q2h8RtrD4|Pl_24Ty&dapC}4n`8L#<+pP$wlEaWNmbc zBSxLKDRoUjVQk@adKvMx<;FmOVp8^Oo+gSd=@oD74Z%55y*%=;W@>Ij6*gj@Y2Ykx zFD~zL9j(1E6%^VfshB^|0*eVOP6fpnqRbR|PhfoA>dTd%K8mqiUH87i#?si*QnD!} zpH+oip~Mbb@xa#Nz1aLU#C})4qnarX`Cc+;eZVwu5{Ot6l`@q|L+|0O3g!~72QHRk=FaLBzI}=zcR`4Nsa&JbKfBH6ps=}b93d(n>vy>)1VG7Alb70t` z68I8DlBuK~uSkx>d>YS0vzyil_7q$1A^40f_&u#(m%dw_K>e>UcGDY_8*jf}Aq}|F zN19ldzNr{j{dUv+8h1C9;B|xTH5X7Cet#No*BGkvmX`6MuH*VaIQjurbb*K)>=&kB zJduKhWkJ`FB47joq^rTvzyeH$>A<7`&<(Fs9VSx<5T;|CcbdN2AUnze?iMFC0)zwZ z0eX3Upox3(v`<~zThUX}iHc)2hK5W`|5EJA#ubSbhJxJnJTQm!>>upz*VkdS95s! z3%YmSj0F|*Z@%tv;CR zzR1TsmV(xB>{PT~%k5NbJ$D9{=bk<$_he5uBReu)7x&htw)eYsItzpuQh@UtD%!_< zD|Ek&^&$D^%ysUCvyqQhv?ecF_{999TzP)^P)l}N1?%-$loDG~8fmRByjTiz@#>YF z-R$Nz?WL9=H29c%wTSm;(RyKKJs1bA4H9|L7_dcN?e)(d%PY3gfAUv9PF=nJ8!hP7 zTu}7yK!q-TkL$lo1<$y*Xiqr8wh$fBg?F=hi5M&D*N*xu4E2BelxMy70WyaztBM=@cCffm<8>5oSykBBXO{e$ zKDMli@PKu1F6qA1b6uk;A+TQF}{bA$5LqGh&Dyc$~AKt;U>4!W82^vRQdwpSa_lg#24j>IzK>g@#q)H6k zSlxcPr z8BG9-LU-+vd#ftdsuRo#vH1cZNyAP zA3ur_$oSu1L3@J&a9_dw1$RXMaD*GafOm(Wbr80mRcE>z4(KG1i5Jb@Dfwlz;mBfU zs^}Z?rIVOu-+t75I)58}{jz&5KMH3kTZBIl6km2UzIeLjcAV7ci`XSe(`qfY498Zl zy`ntMyP<40SNt8-6*_-sFz%ar&@4sLq3gZqRk%gbAMiv@QFJD-7fl1_Fs$GxyUQr- zh&)qybEudKdI$f<4_v4BTlxd4Y*&|;?N5d2h3?ff9*~BAv`25}`#~`D=OCirFa;HY z>j??Ic`ui}+-%){z5=EY0P8JduSA^76m-!+J9wn_B0!#&n2ker=gRTRH}9tjs{d3) z+we2JWjhIwXEBoc{`v00##O9n`9X%_hT!&xl=Z$!^FK$vfXF>7XK$b|ll^)b*mj%5 z(qF6a)~7Z`QrjY_*E(DG`{5nz{L1g87?8aQPjDf~`sFvekcA34lMpJt*BXoT9R)QV zDD0X5sqw{f{PNDhR^#*Z{hgm$lHn@-b1(nu#u0iztkF8) z-tf(SpJ-S8aYyI!M_|Q6U;RSTNGLv;cygDJZ7TXzhEgQgOY`^FSKA}?Zr(fjD~cbz z9#mYw5d<)~5cX}t>iy^(IbjcuZ&}G>)$w-b5d>xb+ZpbE#qsOC=d35jx3pQ2oFS*L z{QtH{>J^E%M|iC_x^%dWi@J2OFxdYmYFCVgyP@JoPoqKDs22wNxi6n0gwwoq&`ypf#oI8VPApB0h(0pI!BdvwFj8YSSxIg7{vNVCTscrqSoRaR} zM~My(6a68d=&@WPq7*C23O+$c>}!Ch&PtmVqXW$_etNF8AULE_^)@vTRM9pB=jC(5 ztPn^Qg@oTwZfK4&nc@?^bdz}c_8Xu!5=loy)gUq0C3bfCj& zuw}ouvfm@j{+DcK9Zo+ty>N2LlmJn1j-#JT-O*Q~7%jd@93(%^z#J{+#?VtU;_gpnN=%t~4i_9Zg{dnU*hT1P) z0hzlIm^sW(3^DI&)x}6cupn-cCn@r!`N;i4j6c_!am#Enta%TTGUIwpM&vd=x;#?-#xo*603`G33yoESw`S>-lCH0KqH@e0! z!s7&E*ql*%6{*eR?yL81ojc*8Vk=HB)7M z)C-D>m)*t}DKMoKLq=!hxcR`f=xIC%+MT7{OiD+RQ%s>G|a<0Aj@cZY~((m5`OtLN2*cz z5;pO@=g3_g?IP7ACAu_B)H|PODT%%`S~)%!GL!=iEn_I^nR}^(vap1obDharFgvX$ zPM#@_EDY!L2#w@NtfWt9kSDObhj_#+$TXl(ZGu+`?Pmm+qaBmv7;Le!AthP@(gCQ-r~Csc zUDQdG;PNOXk7W9;%9RveA7aM(YabHX`b*^fkV8kEzpi7|hQG7C4=0lx!LnGo)e=JQ zLeYEM)EsJm;(|xL@17um7r|`jp%mZ2lWb^5h&jcU61LuWa43SOJ-6&5Hr&q1m8%vD zv!7}z;L~gNsUx;@D<|cq=<@_2s_q>|wavjY^P`X9_|&g0!5S~nt&2O*e{Y7{HzeEM zC<<<}Pl-yNh#OwXvsE{~L>5L4D;CZAI<1UpRlgL;Woa)IyW-w}-pCq@t6#kKO^cO1 zF7B7!KGrW@l&EgIX3$u_^!sDIVg+1}`+brv8tau_FZpEkY8iT56{&u4es3!#yT)Cz zHc!2OtJR^N7wYHM&s=#*DCw$S?3mpdYDjFlGUFc#f|H+6O*#%0KX%%pL%!+_ z#S>L`4XcaTtazEBZE()LkvOdRyOZV zS2~m~?SYafwwXieYI)MdbCcCyK&Lzwsju$3L7!utAv%4A>#*oROFE>}t*eoP&`{v< z?SjTD4UekyI9f=z_GqS087Cn!Rl)o67&3g_B~jjeGDnnuv$)hbBP@dpiz;GQq=ie{?oRaWFA;uu+r32Q&FRFAt!<3!s! zU8WeTyHp_?;?8ZJuy;<(t=f26Tn$H`uJkW09crDpDmX=%K(8{ZKtt)Uh7vTYlG$%U zjXDdP#@wRfc-chV9ph1%V1QuZvky!1UV8P7IdUI;`56Ag^p*6#S~l#|66&DL2JMu& zu6QRwx+kal{S*1Qa&vIzg*$PoOCR|kIo0O1aH>=5cioe=ph~QcUHR`^$Nm?b$Qmd& zcdC7gU6VCda$zNGva)(FTURt)bFAo~mA*7hy~!2Xqb>HBj>M3z8C1TmIaIz1RW(;- zYl7H8@M+goqG!7%_~hbFzEqJ}uh$lbuFN zpTaHbAE|5xfuwhTWvjWEJ(^QF4SiK_dgTzCEOd)69i0IEY+s)HGCh+wnIVTYjdypOVUuAX6zY~J)C!*k@G zlPC9p?&PisegzYB%9JWm!aP;qLg{y_IJ zS=929i$DGM#lr@-K+rk-Px60McCSCvL6x#N1DAO=Gj` zW1RH-53}n>WDxv_Yk<4req(q?Kz(*Du4Be0f|C)!do}ExtZI6+#FQZ=IgX<7_$}Ba zmvYY@Vw|M0uj|9hkjXe+U;#D3eG<~3o51oY6WnRCp6$WU-eGydO{oT6mwtR2#WYky zKVIe5OtsT;Wmo8@w7dxy)WK9`2DxVh4@9*p+L*nh`yMjC5n0ApduV90MPve*txd@d zzhL~<`KK57V~W`t`QIt6-WIFfmf5Ns0(maowV%0E*|`18jjM>0`H3R7tkLN05)jC! zLGG!}8hgI(#r8zy(YiWxl>Sud&w=_=;g0y5Pn;TC2y0@A!=R(J%j+I@Vv5H;LTb?t zcRr86(P7|~1j3?G5%R~E_;UwmY}lvE4+_73t>^cO%pPJ3>GIy;_iFb&w%}tGW_b+B z{0rF07K!=h8dvEN3jTf!V4C6;jC$34yQTfQa6>gB7VWRX=Siz;OKSz+v({)gB@vM1^)a9MKZ zKYoaVBq$ih;Qm$SRcjCTHSTdl8~sZ@!o!NN%(Cf`#nbs1w=2f?KbOU-e1s-NKvTNF zi8Ws2GLlnbk1bfN*6^xw$7u^Yc?*FSV+*b$v^$iA9ibgf7Fbf%r>+yW@8v)5$!BQG zMp!XA%ZFhk1eS!nJ`?=$gWTF+zHnrKCzI|GV%i<$>s3;|PD*m_1sUYkp;U>zh=XLO zIgv+xU>^C#OS>U%qC2xQA*p*6_h#?Iln2XtO+H;1f3hmiNZ$y~MzsRDIjoQSqq?xD z_ZyOR&I$o`;Ob+^m381&9~oQ1ywmi1E_Fe!f{avggsfIXa(^K=UA~;m-A3vL@O4pr z`y}?CUSN%{MQnwm?yqvl1TUS3?F`NB!3ia~wrQNyt$N*Q``jOmwpg>s1ZTV-7DN~S zuY?Vi*v z4`z)nsA)RL%vc5UdkgV)4P_d0L$J*Jz4U$=R|6zOe4#Bzo(uDNMU6@fu-Uxd(*1;) zm2Mw*R^0e6x)vGE!41!fb^5%sWYn!WFpfYcivr3%x}z1Mm|dT*A`ElxwS5`rM~*3Sn5Bj)?4apm^9`|NVI#rU&a)3kV68#+ety+?ZcC9>7F7sEUg9u zi;VhMqCH!r+YKT&uP^t~3GIk{pJqo!H>&g2X<3vRwWBk0FGaAttH24ZTNfsfD-`xxwHy_`zjDh#gl%ShEfIEwD4t&Tme`}6?(OUtTWP-HL5JQGsSn7_a-fC z!2K@c#3b9dBYHlrevIyEjBZR1>A%^OJT=m19iH9M7>F&26ILb_f>Vp^Zz1RAd~V&H z811c*)>9*^y>)HDpw@_-ver0VxV<%A*m`Qw>ZFgSpp^>^k%&APIFg|W4mWEei9Rc5 zdHPb=WhGy>Q~W+bi_S55-@J%R5B79aXaf|LIUdgYO9EjT?QvxABjVW`lt;#6k9|Y>_R!^rc zs%fR2xKEKpV9_r*^Iw=Q3ibt8-2xs;zsw|y`c0g<()v(vUBDOtYs1*Rw`i=`d#Yvh zPYdafST}2m27l%N4Bb=$(hG{3A3Wr0>Ia(ns8@7FT};$r`+ZAMQ_Wv}HMLXBiCr;g zSZlpy94W;h)oorUmtRNo zG;&v79nI5$^yBl4E^u}iIJXO&*9B@-sb5EPYaY5GgfN8mO?Ix>z+6lS1~6;FbRDT= z9;(mfm5y$Kxf8j8kPeK+%5xbkfTuc9oMA?49S_;$kDu)PC*hyfa#clQOyh8cU#`2> z>F8aLXv+{iotkx)PyV$iG?3h^lV)P1@oPu?ibM1Qm#W64TI5n4V5#=oDb)tjr>+`Q z&{Q5qc@@dDzn<8^@ag6+!+!_W4+fiG3)2n3 zGpFXzX(sBH+QTDSF>DALrYg;q%-k+L!t9{LSF3`{ z!GId^aFA98{vGNUvak*l-VpR{vX5WEX6jn9BD@KcUV==Blp<~0wgYyuG1WC}F+}mV zAByA4U=`VMzn*RGovs3}HEnXEG54zbv#1LG!lFOXIFh744U@95nS6@YEMqxkqu$J( z`7GYbRT2Y{>$_^I=lYUf|7qlqXU+(_#h$5jP0-R{^*T%QOHD5OOW#5jy0#b%+O4S3 z7JB0b$ClR8{EFf6t$#Ot#`6s2m;Ex|zm;j` zGKq-2t*&A7Ond9|>G%#_Gre5tHz6Hid>m)#kJ%~xF{I}jYUM$~6fSaCWfdSH$A%$* zBf7v*A(%`rsH`Yd(Oe!Rj9Bxi4w5d+{Bn=szPUZhUB!!WR9dLFTp3AWEis^*QJ@1hZ=y2ET7XX zED_@8-(>}O>)ST=A&Y5z6x_>hk+vHD6+d1zulos;JE`2 zFQ7YYK0?N*0%sE_cZytcxC_@%xW@nk?*+8(p1!`7k4f*7WcvH}DVpN4Mq61dvzeQf zeyt@F^kg1-+8~0~8DM#C3__%i-q==T`jPV{lg+QN=mEFxxApF zKsQr$b*DF8fL&EB&0jT2z3XnN_V2ojlzO+=d*1G-+VJ|aS9fAdUDL!_=@%o) z6Df;A@_NOUb+j|~w4}*gxDh^cgY|@>8(b$SDyjYo@1qbze4}YF%Z8fm%z6Ae%TM}QZrSjtnnn#STRxZp zM(WKLB2ifp_lqlg+Wd)wtADFu)%GrG98)SwxVXLwV?naJi)4RLotEp=Ov6T%Ev@(N ztsH{KxPx5!$~IhDv*2LEr69TDoAVmKc~RqK6-fqMF`>y77hnF(OJ~iwm}d$esVpS~ z^`Zfaj$4Uah&v?o50y)Hqi~A=)pu$Xroe;&1|~yL-|KVV8@j$v2|+4oBl_0LX@uZQ zf-bl2r{g2(j4+zAX*5_>yBvrvcrN7a*#%jc(FLWQlIw!?>3hR5`G`@%O_}JhEIp6n zT&I9Y_q*aA42u)rT-gnNIE3pM+4oVZQ^-Lli+XdW5%j&tuw55M6Sl7dJ0|ol&^#m5 zCZKHw{Ywy!wBK6US{MrJ+zevxbtrw+p&?O^HNf&8(FKm`0&7B0sS<(<1&sshEfOZX zV%CRX9W>-J5#q^PuEuFMdPVAGOm~^?@dZ!5ruFYCb>yK7--|80qKGIR(TlgJ!s^f$ z-qWK)XQmQiSp}cE)uOp?bCQ<4heAQ)K9x;!!21D>a_Q)w4iN!4W+Vt(Y&_(BgKH*z z503wuEo!{SQ}x>xKRLVFJi(C60c{6*JFiJZH|u=ab(fm1_v4*S>|L-Ww4co%F~z4iO1U z!q0%EA*kLfGa~hAe-ta^cBsh#PJQ*-T<*amJ1_ z&l36ty>|wMQub_>O>)3FfXawf&Y!}rIF}#|lQ%4wx5L#`iW4;21|#O(jVGhYA<1;3 z5#c74>x3yGDh0hTnXfsRbiua>N7}p>uo_bd^aI@WFuEv2M8hca9BXr>Ql??5U8BF{ zK(eR%2z?&1z27Lz7<21A=~Y@CUK(fXz}>T#8?|!rvU4coJPKyshZtD2T2=!TG5}|t z^C26p2!+Fri@~+aIB&$SoVAfS6@TgmTmH_?2QEp3<395Ymr|_MZe?Y-Jgbwp`#?J< z_(C?(vgsbn)9K45y%$xH2MG%HCp-347fZit2m#QP30Hs{2@nPAZ-`2v}Ly*bwMv~K%@D^)>ZzR}hC)Tp&9+I5tUamqq z7;gDewT2qDH9^5Q@j$n&lSy2_jD3=-gA<(Q64WMrd{$8P4^(F3Xw?ZWoFCnQ7%mMu zMp|7^UrCV59bbfBN|#>Q{&v+VhMg@Ut8Dg*63Db{(kK%m73Q*CbYSpR!y?vt4dKfc z7lPNmf6;)?i)+?$RH9#JdHWA?1KrTbprgcigE3o{MY4asPyYT|^by}}>d7**k@Wwq zzsz89pU3FU(W1+>CTjQ{xVXaJBnx`bj7Z~9?YM6jk{!`udE|x~a@LcGS&;byf9OoJ zy~!mA8|Hz9e%e&5!_`sZZ?uox9%y!wZCOl=SPZ@RwmoP^+LW(bx>Yg|EH5L;{V;g= z>xQ3+Ibu2dvSTTwIaf-KDE?xJppxD#T9;rw)_j;8MZ)nzMMlu!BILLDEo;{)4nSq#1@%_Y*7_a6>938-4jR8CoggGh%HU;XQMK;tF7d_ zzqZ};*7vlP{15jRI|_vK3N55Fy90i{H#V?gSLvIBCq{%Z*<{{`PyxE3bTC9*RDR9Z;9jDUjR6)jx+MTReOAj(@oa6zV-ul|+8Q=Oen z+)|7l?vOvxkROppKJzup^%8_tk5sNCoz62G{MzA6WM1uXWL}3>ue>sl4Fxd*F%}BmR0DlW+rLH=( zpz$8pmF~cfWMggBr1YF)IEO+fI}^OJ4>hVSsa{aIhS-fmx{7ab#h+!xpV_nc?03d+ z{pgr^skXwFH+E3l`bdVGPQlUxq)Y%AM@6naYE+&ox=%OyN=Yni!_~`TDh?juL$+$RO;DTx> z<7x_UyVltA1r@weP{QCw2n!R^ zV>C0WyExF{IeaqTCvo#t!wVU0Frae6VL-orH&@nuw~B1#M6$-uep_<{SR?SLz1mAX z#D|WQeF6$+@D0IyY|+CxRuZP4OXDv#VWoG=S_^W%kZr6~kmsbC-6eyZm~?w*RYrUx zrkt^*ra02n*G%^!(e&D#<<8y|Yib)6Vl}B{_mgWV&focfI7!=??hVH~tJSMU5RhB+ zDp{}(nfc#;5KtUbpHV^I5P2RI^q0L4tJ`AZUi4mMb3L3Y%+9@R0mF4^u-+PPc}oKA zgtsFx^lgr1$9=>Wos)?qOKCd}+#d9&3}nl_-7?|Oxc48f{7py?IFuVBI?%uTIZh`d z??sjhjiDbk{S4v{r{fCyu8#ZpS5jQ<+~Ji{_z@ATQRfuTalE5nXZbVVQ`7Db>?C&m z_|TIQQtqsxs9-mis7Nn{7sM94LHtnY)_98v-CqAELbuxUQog!D1abx!# z$D}1XKRt=EH8yT*gcGV;t(cL8h(dKv)w-TEZee-G1W{<-`Rbf^+Cq8WncfFRrP4z= z6t0t4_uXLPvm(?#q3-qHGU{S2N=fcN-I5RSJ}5FoJo`IztBO>UQe>f5s8i#~;xA!C z6n_z!-6;^Ot*0cKMzGSO#{^T7(sem!S^9OBKfG9&4Fq<5@_>b}&4Z&6)i2;;(0HbF zQ?BtW7c@CSRPq{a6%dEcpFr2kKPtF!T7GVw$MY&1g7Z0X(TGJIbXwRwW(INItQ1SIuVXBLyvE!bS} zFSLtMPTkV{U0RPQrFw4J^+e;qpjJ0BdWJT=%QdtGkBDM^-Qc(%ALNL;Ie|hGH&Noe zo5DrjI2oN-zU1RfT1)p~_2#;7dq;ql_#9&Klxv3E!?Vyr*d*Re()K;yYvB%<-L?8*&`c@g1Hv zzbnj#oy&&cuV8A`mZ$Y_>kFUTJ_~p1nmeIHiz0$Wc}@tuc4NRVa?qk*XZiDN%H%D<~x7Ew+qMt816Jjx?glMsrPQATY>agHQv$P2rGX2yPc;KHrEN`5bKE zL|ut)!NXQ=x)d631coz-BuD0PJlt^HollM=m!v+9p!3-gEzL*iYf-wm^(1YuHuh`B? zJyLl)AqBRg`HWK+{W{A(@l-|04P9Kl=*L8_(nUKhf7azAKWmZmw9D89O**I$%deJx zl8=73MekZI*|<(WRH=<^I8ZkS-`k7&A?C{Ox_MiN2cL9HE6kb2jdtA-A;{J_x?Kl) zGT!WRitcw8FKxLCyh?oXF0=fM)&DNCyu-4bcVgTWPuufMGRwmR zX_oql;V$*nqy|?-(~xMy%aBl;4dw(*zs~Y~TGhb$A(1(kNV%5X^0!>9e_yXyT`fCG zctL*^mv7mQeDudHdRNQ#S9I4*ZVmckjl)nY=UhJi+ywWvyoQA4G)@gvE1e?jE+lXV zgl@T9@iMD663RgeP#ADU4rZzU>3XIb_gRt}y^oD|Ffq8(IRr<-@8OmJpejrWs7|h* zzN^(t1U#zidrb(23(OVNmIda}S1o@ulkKD&hvfBPRN>Z;hn>k|Wk18hlIhyhPzXKy zzaFctZ`w$Lt8rpu$@)WZ!okasN!*Plslw^=^muox37c%s6k>_xaCbr z2a*x%G$JI?+)cl*Rm0{=u*evtp;cO|7MmCEt}~e{Qyh{kf_>n_8V3iuaq!=Vg9d=s zexcY=gwS|UokCV9Fj&vTB;Kw~Q@4?DYvo;3+B7KZ88mm`5hb)b3%Q}CUIFJK^^M!f z3T~*+g*E{8<)}@+&hot%s%w`6+q{+0VH?a)IXeFK!K=Bb|5}h61uiOUSbFcRhO>WZ zaq4ZyX;xsT(q%00uY~9a7&yqfxFXsrQ*-oHNjID?IB}RG%5s+Q-FheEx`+U`?7X4!emo+-Xb}pql~-`!S1k37 zDN@rr^*K@#yOnJo!e_?ofx%2;sxkF3Y8Qhv`#Iyv<~H zhpd`1BFBrmL0TxoU%*KezFQI(>C21Ul?iqv(qqtg!xV7J)R8IszR+ zVkGWIz)OT~#7H{GrTB@ZxZ6@_QgtcxhIh>Z>^eO}P|bq(Vher)K%GX2ek|;21jQ5AWK$Gmd$f6n?aV%z@4)B7D?0)IAKHR zf(ZlG`q-ulYl|D-q%(lrBgFDn3B9}P@hVE(pH`guhhv6+;NU~Q&hiJ@$+^|Ct8Os~ z^TatvB6&5#!;0M9sHYULld0|dUaob^dHLMVwcP0LA@P=IehAwNYmTP`Kj+@3*q9K~ zib8#^nF2Hr)y~k##wA_wi2h3Zh;5E?hdT)2xtX9$!#wiK%#VWwC%fJ#;ggJoX1^)=bLaS#{qQk8*Mf320beywPcQjB8M>-uvtlXFqMT{ zxLe#FeBIJt`JK$i&|f8zIaTdh;)w1gE@cwb0A5E#Mo$g3P%r(TO5Mn@PrlT*!aTIi z!v@ZR)wtnIBP`bV-mqsaqn3PulDHFgyOOU zmxEGL#bwFm-P{W8Qgy{;-QKV2x(;ENyry9bma7`E$9C7$J54P0wr9Dh@Yqx|tTCkm z>`&#%kqyBQ0h8&e*d(hUSB3mkAy^D8jV&2lkXl)o+PK5RGE-Q3cnwXb@$P)RD!A<# zWr4i`^`YQ;=PP!;dz>$qy~X$~Vc}i1{~7JqiJU|G1>g|ve<*mxR1-%&4*b7D_^qeA zBmA=Sg$Tdzd|ZK5WjgSMs8aDY%~ogL@(BL3Ls{qw5EKjW_){vU4C4(+Z{5NdnqvAi zL|IG=CW)M>P0j8uNKKeSRd61HmV6Fp;30=&^EvFVwLVvdx%rHx0p-clA_-o&SW3o2 z*MUn-{$=LWCa}=qhE~*Sd24naK2bOOF*S%iM&kf~PcRqUxQIIvt~09e+Ww;=jR)xd zZRSY2`zY?^YSTKZcyVmOTL78835~6o3~j>fzNRczOy=g|=3pYQ&%rz36azVE+Z@yx zXkGjy1JRw|92^5kxfpl!{|eZj%%R`r;6WEmzl9PGK3ec1i^4M)n}amqV+;e>;dzzQ z{3*wb&_r?tp?dx$nc7j-^lqlFc>iPFXfjfnJvKS29FDZij3zY(8cmKQ}TUctYuWM z#Pu$t2Ej*sE~C?w(Owp1$}v;q>5C^MZE4eAYnC{|tI)IcIs1h0nsUqxd0xlU71y6U z$BnNI1ZS`GyR}CLAz{CK#6W9<-z#}~GR89rch1M%7WsbSd=<`ji}M}id<&hg()q4% zzC)ew0_QVESreS;e22Mj6P<6k^L@?vs+{jA=R4Z@4tBm{o$p9|)_oDluss-Xj0+y5 zNVzc_WF|0%yQue4yVaX_r&ODE=L@O#cjpVK_q_9k)cck5?cvhh>3ktA)6O^0eY@8A zLdyEi7t(U3^M%yA*!eUp|1wTu0ez*sais7!rOB9}-TT!0XouAMk*2>U)0drXXT$l(XY`0%hfk&v6~D8nX#)k%aoOlgW|4}?4}s$? zDbtaaGsOAAX4}X4a%10Oe1)*=xUTeD)>zHL^nvq*>0foe9_im9EZZv>hO(1eAxQ3! zvtt5}elDDiAFuOX`m=_x@ypnX0SAaV?oR1&wgb%QVX@Flt!9Gxq_pX8G6B=ixJf-? z|FLN5egQjqIn4Gu&^Fi42O6f7VRbB#FPHJFVMgv@fjaLARth~gY+Ik_5uuHdbbU!Q z-r6VL9@mxFQ;X};9FKXcg0cs~s(xd2d!#ovj}u z^VP(bj;r7(Jc)Y{?kTvHxb?W)9GYOkl}Z1aC{C2@EZ{f$@#!9rw`eIa|h56gKv1t}BjYi<{3W-r-NG3jPD$ zUhT0$;avlvM@{f|14ofmD3sv}g*|YE0^D)ZwBUd?rGyfb&IA`j_5pdYknkyK z0%!UdxAi6r!ez1}BwcLaum@t|P8)$$#aA`+n^9onJS9=sLCg^IbaWT(Pi+zCx zmS~gfI5@eP2A8sajkM0KjP&FwDtDa9O;4UC_(TU!7kq?+X9zyX!RHAc=-}Cc`#N}z z;2kS0pSglRba12Kw;eoB@T-E^^}9M!r^#14(+9UK06VaJI>eGsE|xYcHM2%fo?-&4 z8(U27b^dC}+P-;VPI_r44VlMDJ8SgB29tBPx|pLa=E$95-YHU~FFR@(XCVNiFC?;_ zbX^(y-m0PNFgk$jGm)B8IV#dL8sXHfLW%=}baax-^_^8J|DSVul%G`-C*$7K0l4+J zgR;jbMxIxhxKt@?T*}{C%HQmia<_PDO(gSkbRDcFNCMPDbCLk}W}B&HR}svNAc>BS zLh2#X5L~C!a`PD189S{~jch3q{OPl1MRu39$hX;{GgE8`PH-?CdqXhR!Spm9$8s>& zh`EXGU^?f9Kyq`^(kvT-Pq-8S%w}Ul(8q<-D>nrHaN%snHUzH#tLZ9cjXrHG*SYn& zY)!U)R?F3u2if6EIy%1D+Vaje(eel1?n%p97QvO?c=FvIoXnjHcxm>?j`3w`2^-Sc z1NEtrPu?mESop$~yM+;QcaB@uh+dGfr*^a*B6&~QF@3!o*uPqDG+QPCjOi~2iMS)C z1c!_cXxXUo%jWVC)qZlN;C@!)$rj$}!iOt-gM*I{{EUOE1h+ZZc8h;4yb694?KG`VN*AMv` z?*0BKAya*NPUVog^nl|f5&esjx+Qc))4Sb}yc#4c4ar;XQ@%Npf7g}u>#(e=ocI3F zJI8q+480QICDVVD^gRBk7Lc-3MGIl^W^+(uAEy=yK2|Vj#8dA@n*N?gWAyh(10r|+ zT-3T{B|fczh6MY&jMot0-rDM(@jJ^^s>}E_;5eqGUFM$^HNDoA`A=QuCy_aZ8L+Lu zq?z>YRQKyhZRD?h$z@&FME$ocpzD^XzlnhM)V~$6Jyp#5r?5R$qGeEfs*jwGln%F5 z^Vm|;BcA1@v`q3tMa@cJFXc|YQ{lmPDu3>s${l3?~YUrfAyQy zk(y#4dVwM|))=NLW=cn^Ug~UMqMU0{0M5$+5>eZy**QQvRSTJ8KzPcT32r+~1vh%B z8wl@6Ew--|)RDTU3%nx-dUtBz@{PaxB_Hm(kC?ib!1mM{{z9m=iqW2WLQY5OuQ-Ar z)EXQJwGpq=b@Wni0ed{vYxr%#QIZeL74+{AGD2;`V}$w?pd)4a@XE0xH6W^l5NeGa zFC{%cKc#ujOYIL#6x~KNpfU#>ngfR9fWvaYa05a@Z4c&fw(Zx>^HTMMccdEZs}X8O z7dR~h9p?-!GQb^~8@eW$+@_;tQ=UP>L+W`GdC0OF@Uz7A-aFg`_pSRlonPOY$g6Fn zis}SZQC-5fUf@}e@QsLGYNdq>QLlC$(M1ZPi!i>?!ZW%c6ROCM{iB!>eaCG_7--D) za6qCNvyC~}m~D}RjoEHpYH^I&W*uzI_V2*{WaG7O8)&TdH5a2s-0M*bHcq?71skXR z1u*=x8>d}KNWJ&Z{P1>s_MD$ExVy30W|xPt*>i#Y$;M?bFwmIn*#;Vqo$MkTk3CMo zc@|r3z6B6VI1a<-#1rYO(ILL|7_nJRiF@!N&t2a!)DR{y1&F)0GkRhxdUDt2mWWQb zQaZX52u<|O8F%wlI)s@vKnKRUY|0X-L6`ZX-6)g9Z? zW;QZ1rT+L(1Nn-ZivwZV8N?V#sn>{`0U9Z!;*wWV` zf6Ja!mwvDSw4J6=WCW*6t0UaQI5_Fwq*(6aZJ!@AvvycA!a^m?QdEzsvrUf~mvMVI zj;Okhb)Ms1kSj>_zu?%G$F2VFl~U}!J&L_5SM0n!T%K>F&M#hJ`R0h53eTX}Bu<@- z0K?=T@jo|%O>AlLKG1H;K80h^fj&4MSrzwIqSRe9)?4YdrQYbB@Lqp$M{UWm5w43R zI8tYI6$)I|u4o34{o)zrz3cIBl!MZ+?G;PN;JgDQ2jBp^LNCOk?p$haZ83xh(+PyXmZKT58Z3F zmT(S=CVFe?F_5OpOkDT`mHEKVi}{Fg)r22U_%}N)vOOrbn(@etKDs7$*d@S@nLsK@ z+VS1PcR!_(iswE&3I8{?6wKdVF-5j_gL4lg0jIr6z=g?BrAaBgTa-NESvp8`ywygO z8J?UeTSkd-5Nk@W*;Kz*_v$~dD{}wX)Kz^|GhhAO9cR<~fkj~dP|a7*U9&DtZR`=E zx0JaIlZzjHCIylCRk6qRGz3QSC=Yn)`NZfGMBx71Pae`}y!-4PqmjMeJ;dvAANWNM z*M}2G$AMXiyD{;77>pC`to;*e|0zYPQc=$;%?#r%MCnZXvOT$?KoO!Tz10-DncKdg zfXlOAzJSWGfIsX;0jV`fjfFG1y*8QJGy;i>sp@e%0*Owd@~~NgBCh2fI@nh@V6$#yQ;ROFtQOKr5n?blM#V@|RnZMbR z;OQSk#EcT$wMp$ymKYxjiWu3k$BHA~1{>Eey!i3Lbxp5k2K=|rwd2x*59WPYd&~Ut zgA>*38xO)R%)3S23*{mP}i2w9&k z_Fm^Ozj|fD`y@7Yz4ee-B1lxf6I=K-cyhL{w~Z9rP?Es<8l^p2?4xJJht5w%wpMSw{Gj;5B7|P_rqswOSQ>+(69mF>%T69rP}1iL6ai0R>ES@N@=dW2A*Tu6USCj@8jmz_n|i2 z^RH($SXGyvbPA%!VDJONscG+eugTr|%Z^2Vhv9INb55A(E`5^{LycJ`GCjoHvtovA zC(rVza+^s_m2}E@V&ghYM)pJQ%!I?One3!pCtVXJT|&|?L6=9iL6`3mw+>s9LMYxk zrZZXnR3i(sq6KKUzc}XkWv6n~ ziLmYs!luw;>Dr@xVC`1rV?aczO1~vTXgEfigBY$t;pnG?bo5d|>Jtn924JIxDTYpi z4_2&w(;LB%+j8az6d|^xH`4cBNZ-q|)g+x|{i*RR|BRB%uNg}!;S9$3Cl^|`O>s!l zS>7^DH5s5X7-yZ?ONo$m5ze&;Gb}=WF_%5n#XH3fl^mkTx@f zIjSotv$wqPCLRS@kAYVRmU$Aq7~B|^)%CpiS>fOg6-6BTb&I-VZ)Jk<{brUZF5qAn zP@}@^s`efV;O1W+%|lTA^Pd>AbCqr45D5DpXNVg4 z3kcbgq!$U3=5BwpJ>(9l1?sL&Y~ccEx244{ze)AeTaQ2JkFFo;#?!W>>^hu2IK!3y zW@*cZacmT7F!T?mSN=yGVctl33&$a~!?&!7V5s?NOu}I?0F5d$l~{#^4dO26jRZ+5 zcX{{27M+@MTQ<47U{58yXVJ24+#?b3grzjGP}pobD65d>ZXsn^G`J#lS7*!Q9FgtA z-Hh0RUlF6{ptFsS5Sg@9NY6h}i?b?e^W8&SO}^dNDrR({24OWDVBUFP zfB3d_#E*X?(r-Imr0?oy$Igm0RvsVTeONu!?mis;E9MHs_4xwDu7YF0g@rc=Xyp-J zCF{Y@TQ;@Y0Ofw`UnZh&mi~QS4y{VXXo6t@v6;m=IOsT7tP5~4mw1{=vZg8PO_ z&~1vh1Q_(TK?(0(Q>PD+9q5p4z7J#)&IdRoHK`h)&VM$_C2Sk6eu4`?AqShx zEezM)3H^vNs0)-FE)1>c0wsUbFNqMp0eGUf%`R0ZB&BQ%e)*=Pw{~yi>dG3s&u$Nl z4GGR!&qjTxn;Rcn1P*RqMX!)m5>3gG)8=3%D#)VQQxp`^s3=DxkaQ$6d;>#9E9U!+ z82PHB^C_xmBKZdLw|X`vLYD<@$pwW(w*#>4Z#Y>qDglP=M$>CWb?TK};>zbtaN~xa zxGpWTF?U$*QiKv;4PM9`t~)%(HM9iFRLIr@FKAZ?MN2m`ilk^TQ9i-4^&<7xIRK3eGE<{OOd(= z7t(6Wqn`*;Az~_0_5|PEyCBD0Ce4ZjIXiGhUf$H7*^*WYLRIhE^AH9W1Njgp0!&Ii=bF5#1us!uh*HiN+)9!>iZ)$+P8cC+F zoXxdy1P{YUE|$dLQVDOaoMn9YQcgxX%jY)q;KP{{A!yQXHKk^z*>Q1iT_~*>XIq=+ zS?R}@HG>69>#+;h+RAvW%dx_8+<&JW#SpOf@G;j?SlEgst=QA`aEVEhyz+t2Od1E% z#jrL6y?!d{x}(95e`4?eg?Bi(T=1VAj85W);1LH868s==Z0XqpXQe(l*fuy%eADW> zi1U%o@&#Y*QP(U_V!W2Fjid(@=vl3yuV7nMD|(yPm`70t6K*rPm@|bvKC+Kf$v%~h zq(13v3bMPomC?@Dpv~YzUf%}EG5ZN-zYAt_mm7i}5g*C@&RkPWa_GHtuh9GFq#o#H z#O~`Du&ymuHjd_Rm+ zh-{C!6WCr3HZp&F58*4O8kx5_*vPy|aCb7Fd!CW`04sgJo~38c=s{-h{m)aH7it`( z(L8dL{w^itD2-|g{pSp}F4)7@I`~>9qqc1au#gn7W#=K!rzH@wyL=J0c3UoI`cO}2V|3Q_lJBl@i|mf*o{yR^wjo0R5jxZ$$rjEU(un-6KJEmw|P+9Hhucr@YEl2$HCT#0$kOZqI&^1v5&qkV{$IovI-kBtN@C4 zW3DMCo3_F&t0IlPV@n3?);b{8VvQgySjx+25@PQ)nfW_HWs)pA+>J?Zp}3NpqVnf| z8hp6{d3t_G-l5Up|4PFzS^Z4Ypwy!_D4SwE#vg}CS3<=v7)`MipBqiH?}B~h2h$w$ z<;K$4=F1JG2J_`c(inVXmm5h3I&W?y?O}0q18Jc7a^t83UpS830E#_!>wY$%SRg-l zXSYH0cG$(ytBF=Y$htJovG{3`EQxI_@7agd$3=9+5QrpdO6t<{`Xtkra6SZ?a*aH2 zmbX{Fk%K~liyLG9Knrw@929$PW>cF+wjCOu7GFoo(5@!f{b&`CrE8vs2wnbE__0 zUpxcLC=3?p`1->(SfJych;CXLLg_0Z<7JVDyN{Ls56P&Sm2I;P{XWT-)i!95CPOZP zv4t-)YO;gfLSYDWJ^+3BcEWVdS&?}SDE0G)pzn^IMv}%%ZX?n)xoNv>g3uOCEeJGY zzguSsN8YSH8e25kh8oL0Gt8B&-q5&*N#+7w<5DvZQUuuNb`;e(hf+|frRfJh@HHm< z_z3Gr@vWN^vEQvsL{b_II4U9K3)u*Qg$mAa0n^3O`z)xoNU=o{ea0VAND>8|irm=~ zavbaWEPu)zGYuZS&JF~#yQRM?N(G0sn4eP+v$p=iodojhEMGTPoxoV<6v9wCq1gJ! zN?yh`PD!8c)2{O7MgEYrf%7G;(8I;PtPT2lFD3k2tZbnhF7Y3Rbdd8^ZA4yTO(ORi zZoiAHzvPpjvPo=Z(*Sz>5YK!FSgKUB?35(WLx^K)dt}Xm4H2H7MK*L%?mDKVIeqN` z0U~7oX1TbFnk);I2_j%b^Xk4-E(KV z3Wj7}%%ZLrvi8u_h|b37j7jfhq~^f{ThiN-;m|W}4Db0vjp5xBxiRdvqcNz*dy@kY zvGyOurQ}rO-% zQcx9q`l0LTKP1mkyv?42!>w=xc!Z~}8eDMYapETph?a`)P*e%Na9PDW z)JTP_=>4*tSH@cV@g4bsg5iBHS{AMX5eX(hs1Hj?YRSA#@+T{y3Pl2qgLm67tKn!s`aQ@SQ{~=njHN&oL0K;m1&Z*!p$Gp zIJQ&rE9_IgN{aoYecc`mwqBOA2YF;FrIkk{EXMKjoyQaXPhv|s?BCVG*5cg`_`8oN z+3g9oP^Z|E2?G{9(YRalc1FwlP#|*vG_%oir55P_I>Y+e&pEy6EdNvvv7la0)e0%j-N;!y&dn z6NY=}V@&ha2hV0NWdj0wVn?tXly$*EgRbc-Zg6w7+;hN5ccx!3oBDS224`AyL+~(? zmF&56d3Mjy-maRDaRSm={>aHasu_+yD{dP{8#ewPn`(4PTLOQl1Unng%8frANjQnR z#(?8)Foxk^gbzyvxName9M}uCNL#Z2z8xWj<44Mxhc1XQ8+t;%Q>Y1**pn z4@K=C2C`767;VHrk5J=uR%%=1^2su$B>{A#iHjV!nk*IJ5juv7qN!E|FpdUFY&Sp) zwQgd&wji;X_O)>?dG4Q>QL|%}ans4Uj0>8M4H*N?U@^%MNL;}|qKbYw%!7-_CiR=o zcmACTtb{rJ0E9eD8LR8O5=Y9oS5soG+!1~47B#-6{SBkl#IQxDi=J5tq)xHldyYD? zVF8;_&^mAkM@er+N#msKTsJ&qR?W-jczw?tvzO#2mt8431m!nDuG*69cfD00mmsT? z@>z}ADJuj;(MeORzwJjP-ea@~Oqm7CHQuvPbG-kDx3>Y0s<!@=u8Y`eYi(`qUu*5ZwzjtA%T^P> z1hiVfFVI>Mt=@G}K`R8b=KYyIdxYO)u?A7JKGyafflNV(da;J;ZF>y2{Pni| zRy$lNUdQ*==FT*wk6Soyip{y61VzO{tAlmocD{dbWJd2YB+ zRs6wKQt=<1J%m6e#Iy(18j|jpI^EZGx@%RshQrb!)#ErRAqI0;Z1>qFqHt{EbMtnf z{Hw`F^LA$fZK+yTzGd8T96pejZq&%jc?J=;pR3o8_M7+u@IPsQ9n@a{j?(}gZoaD$ zCckFGR&El0-&pe<$&U5xJJLRUyKCItd^oft^P0-;NMR#yKS_5rS_(F?2S9d8SdwX( zm^w9V*cyYW0m!=R`;rml*4o53F$oM{98}aD64Tgw$3uLSLl9LCnUTsN9h0hZ?E5kd zWMAj6vG#>xGeWXA{Fb@`yh0mIW46JHzAoVp{+YUS^j1H}M$7AFe5I5LlwGH;!p3EZ zahQ|W?OlqKH!L9wgF9f?-%=g$M`B{%mtn>u# zVy8qqr22K)se`k%QrRZ2#x}knX2$A7Lhp{fC z{p2t>3%F7=kz(8AU&PF$#gPZ zk+C%Jz+|d;MYr(xN!b{ET4fO~L{V1e_{XaGmjx2H6KkS4c?CPp(1KbyST$qWj*!NV z>kaxhQiCB`P#fc2J8PIcESD3o&PQ!7TtB#9Hz-?S%|YF#&wyorBE*lLSr1T}rpK$& zE+H9uO19`vlY&itTl>D5Dtk!B{1rx&XRYBr@}7~|oAe0BFT%T$P-%L&KPy^%#vR8n zixV%9;_zsNo*LkZ{X{dyWp+|EV`>kV8+}*` z{!*Fi)B6E+vU;g$jp_~LNF(5w`3)o=bH9zY$ca_V{l_Rad1iXv5HqjXot(J6HImP%KuD3$y0o ztLtLldknYAR_ja#_Gh$4CM4N}d)~Z2&A3ZA=;-%ebo!7Pmt3KAC^@4Zq@X(5In~}F zr`jDGB7fr|W!&?y&c;yLnwhJlDsm*9N*p+_HRQ{Pw(9hqN?s}4YF#{VOF+?RH5i{pY~V^RKaVLn z*JrHRI>T6V#oQBHSBzTZqJ`phqZXlB`Ua1M21+-^m%Gh|HyW+*WxkO6@3T0h&kw{e z=MDQeA|tKOlc&#Wcw?4(2dtnb4Cwce70>Q}7haWFXQeQes$r>9=}CIkk+>X72a!f*p940x3K z2Ddjlgr)_0+!JTTYu)hpfwdDQKn|ux^aK&saw--0ZLu22`RR8*15sNaQ;ZyV_>-`# zkaJ9t8q0zGU0LDbgJAY|WrTAFvf1C|4rlfZW4@dxkHd4tc!QX3Fc4lPkCuP{_P+vc zyS{`lNyPr^IXYTBV$gcOu$6b~8_&yJG1u34p0%RZ z7pUAA_K5YhH_-_JlREbzE@{|DQN_dUd9>{*V3UcQf+2#fXAx})6R>U;UB&4LheF&+ zV*m160%gxGk+xWe69Gdu9++LBb=Ye#)-1s!j0Ta53%5e71J);jZ65&hCt&ItUQ~|; zu%&CB%w3k|MYRpjQQgo?UnsK%!CY4cw=_z(xwix=H#g4=L_cr?)v`6|4^)0~+jJ&a zdtDd#kjhwdmvRP5H}e#@FW}yMgdB1MOp;LLt1T-#_BWrQ29B_&QUyT)gzL_e))~Gi zIUILT+#?qCaPrD@tL$lE!?M%P{DKjHXhc%U2If=hu9SH8**_!-iHcOs)s(J!C|1LO z+HJp0^^?aa(4F>;fI1P9>9ewq@jd$Qx*=ujkZL+R!wFnXpzz01W$@0*VP2{Ov02vJ z$|S0Uvkei(I zNsdk_;F2*^)&F2iD=&qaLq(-hbyegjIFVGZWv6;=Mjir}GtC%^G+5?S zEDF0apFKzIyPCW*ONDt+fnBQeQhdpAGHi#T-QQCKf1!k+4*H*7IWpK~IMILU~6=A5Uvo2C}wZf+?Z!rdh9qFj!$kWQ-Wn=Dc4SUDrm z$zs>Uq3X^5zun%{)Q)9JS2%X0%3`3?sjLx|wfgW1>j9vuB8mn_p-tlI#Q4muD3dJy z|FUn0}TSL<=b*|tFf?bqx6?OCO{UV>X$3uXC2GAOsZ{i&#;53v4y}+H z_nkDf##N1N4Q2gbIy&VWCIOvLbBQM`0RyOV3@iqawFT=sjl#iqIq;uW7}@mUNzPl+YY%} zBiAebSTdc`psiImA!9*G#g3OeLFhQbv3`y2>MfNB<$6Me_2L%-Qg5HcB?uFt=1#jG zQw^rImj#CUiw>*$4Aqou+a!2$D4^$a{!vBxxt?shlGMWKl*k6GeM{41V|91bE|2!* zF3pMuwr-3Eej4rbi2Iit<8z;hCeDf;($vtH(Y;YI$b-66_Cdz#>3(DN$5x`>yNH&h zqYA_wD#^tHb_CuyIn3YMFODjx{Tqnp((o0)`;DiejN##urhqh7Z}j79qWzE7DEODWP>$EA5pYwLb@Mx?BD*{HmwDEri+&gJo`1bLk%Rt|S*e2u3! zY&4$P(70-&we{1(CDfhJKJ|nhTJheaNo@aQfv8LLGUSZ*n{(m#-v}cS^S&h+$Mg4} z1IYL_tQQb?Y5Ka@i0$_;EbCKsDNKE?}v9{m{y@b^B|>SteNqghjiR* z@K#H{@xlxFzn$Dy$p%~Aj6&}zuUD!{Y*W^gH(1-0T9m5RSNG1=Rt%}pqXRpR=x#Us zl-hDMcy=VD5C`>`XPH;<8@H;l7hbHfHz*4%VvYr^73V})jbIR@HCPxkm#2sbZvX1KyfI~ko6$sZh>C9`gkBWFVDy1@RB>I>UJ z&IlFAet#x6cBIBV!K|#_AWe|QYD(mCWuhKl11uK18TwyZ2YKhcSPkX$K3s!0*l zt!C&35b-b(ZfB};8k4SrJw>6P(|XS2NZx@Ra;}C^+B&L|u8-4Z74b{AwjRn|daQ1a z4XUo&#>>PAZ?Rtj*I296yO7$v?`&Q}X?wgzrEBCrQw%o|J2QwQZ- zeMDe3xv89mXIwW-=j=TqFxDx$kWNd^uLGAtV=p&RzS~)@>4BM?(Ykz;CtTGNEI45x@H8NsP@!0T zzPr7v>3C!H=6>%zKDC@NObeRQkcpo{q6n#W=ITfm(Q!$#@1f3W_DaZbWA&Cm|H~lx zo|J}erx3O{mJk^UG|=)hWRf6J{j83!X7?#mnYo(@%hX|J{Ob@U zZevK~NPG|(daHn(!gH_#ZS3@+;JwmGu30Nr;_8e+v&I@O+ja-`q8xd zL?}m>mM8*hPGY2V21h4PYh7N@6rLokM0zr@+23bRyDT-IK9wrS;NmGML46PrKlupM zdte#~`A$!x98B1!$ahb$!ks*xNpVHQ+uGv3Mny4zz(GZ^XjN!YiOB&LGyMWR+~!iC4`cma&-zPnehH$~Ex6WQA;?7V#)ExTo~1V%tGj9&vfAt1yWnPtzfs~Z z7Vrned$V|Fh&LwQYs7mN-o!GIV-pgVkckrV4JV{gLXMJR(6;G!(8<9JrF{B<>})Cvd!L z{ctJjoQqXQhFA0;JX8I(6i%gkR}NP!+_{`sx^?K)#_CbsyNis~0U`@>w-TMHelxmv z=j3ipft{_P&yZ*hp5cIxQqb-p(CK}T6s-0&L(aCNd(2wvF9GYdX!VtE!N(aZpJyDY zQ;NEV57hNDVMVLpqTkP`wZ1g@mpSd-;kmBZjd^CnE6B)wb(QwgQqBz(?e^8iLWMbs zkcx|$gt$jsMdGSRo?Bz}1u)^o*!6*t(tRySIm%NGwQ)cJxw$}zrRvw@ec;<~cNp7q-u3@nob23FYyx~Kr zk*~J$S>twPpCOio5gIT8(Xur1WohKgH1g@P|H-yj%AQTxx<$6kb zmlU)fst!-*XX!DmhfWHAtMyP-cuwn~6O4{KNut)bP8L?$ z#(iB(%j5!F>L4e1$^Pt&@Nm4?m?h$TTt8K=>Quycm3ImeKUS{d6KvXAr!G=%o#&9z z38@XlygUAuw=vc016MpyV)4z?;^x~))+$dI4szq1gfuD}j${GuB1@YN4cx*5wm&a6DhrJypA zV?;ORm~EfCm>iLRNQ)j#VEQa%z^2y=h>MJNQ890=bg{X`;c$o3I!+f}_TwQ&d09%c z)iYk7t@DGT{j+)@$mqZ^tacC}`i1fQ3rT5zw@jjXF{{!a87#QHz{cZd&8Z1r7roN0h%f^i>;O zH>1_V0d_^|Qdb+KD0PX5Cb;x8n&7JK(ju_Np0{6@;=9B|MX&DE-fk#%|0lmO4{TEs zgN7|Vq2UKW)$>PzWf42fWUg585c5saUk|b(s{Zz_Lkl{v?R}4@}SgV9tJ&8L6CYFC1IAEG5GRiIhO9Fg4 zAbh&qE5x!&STu+{@)+cL7h4ADwSye$kmJpSiFb<5G0CC9K#Kg!rM$T3`oRgibizs} z;b+h%P~j9Z^0jr+y$tX=NoMUIRJYTV2;KH6;AhnUypJ_NH7?pTH8PRhX0>R`M?Ovz z@R4d}GTT-q!~%g%{`M>>OOneN<%Emu^cY1wM(fcwS{GeRW?~BGkT0_DNyHf;uOF;9 z;xO9|bx8qx8hbmw1k)DBEUn2ERzJ-mkB(JH^ThvDT*Wte>G}y$A)4ncDVa{lWO?O$ zOC*zV@3&6XZTA3Z$U|-=oJL8yYSCg{dQ9wIm0KIzEW9JEE^w1$#Ajht(JqmK$T_Za zj@&C1+Ki{B<3p)k!agcpyPLpzoe&56tvV5lscWbT=3^4*7MKZ)9;rXpqi%vXk>G zG?UJ`jhv`f{yXaN$*cbd^=R89Q+R-q zAI761bRGYsq?ZYPI`vZ%yc`D>dyMum;)qRB3fu|%Z8}Go>`%eB*7{qm^`RMEUIYQ( zNq}yaY(P?^`A-t+kcr=veU+@&MxTYO*BYS@;YOie_W)yWJ4rIoWSOC{(|(y8b%Xwt z{1~p^R9PkLUkId7@1q6m*Q#L5qn0}0$tAV1!eY}piiSRnY%hUq2mc+}u9dBw5MzCm zoh*w&&c%-{%gdqKZx|gP$WDx&-A-SBWz`&Dh6mO=)HbHoy&|m)lh4$nO)Jg#~H#F~DC< z8j_lwrB0o^(Tv~79{(1Q-XHlvay~aV=w_DcHVQLiyfUDC6Z&M6H#0~3uGMi^yb{Pa zPgamV)d3PzWUqv|4-RbDQr)5+SX@3Wk7>a~b$`$;gA2yavFfSiGgph;#_4Mt-)5(lh z-9*o)PjICIGL>YXleQe;sJEV}#nG~Pwvt?JYnyxghB71ZPFmaXb^ z74=y_ea;7qRDFIren@==rWp_Byb`iDVqGiH#-Kjl)iN>Ac2HKamT`6!ANupCfVD3m zrc~7>va^`U$x%as-jvCm;<@DFhw~Tn{O|T0*U;8e<@MHBf01rKtYhmSSEe<|S}uLb zvi{Y+^d6x}Flia7}HfWsTVq_BO7Se2%c24n&XBv>yYvNe^s>MCtt1T z(7wO;-&Az&zpJP-5a|5nbiQ}>$xelNY$^XYc` z;xRm_iqARrNEL6}nL+hJAnxz47KY2XQ&bRY;tMbg)R);j7FSkH-2a6f zq@dU{J4-g;RgfT_YblqX)8I{JxI59d=9|Iy3<9T0%ryBqQ=&5oJ}cTnw~kihQ?@jH zCsDI&QD&8#%VEn_fs*XK16HC&j5{2(FP0IUCQyR1GtH5|R3HIc5Ut+#S2%$12uWuj z4`ZFoRnF7(d342pKctuacu-Ftkvtu9gq{vW-#r&Z7I*K;4&-hMbv6i{5*k%W?NWX4 z{8*WtO{eh25$q|;+&=qBurBRQ59_hg7MG21xWiPH{|#CDzsBqD89V8T>C|HAc>UUc zRg0m+%VSL(sLj8MzEYcaIZ+u@o0&k{F^W~I!B=Y|yKU_8s}YE46VxRU<1#E#(xJe*a~YL^wKN;`+i52m8690Jf5Y4xy}rb4O?&@^92azg7Xb7? zNv+IcC}QYPuh(~l+79Uf|Au7BDqHSQxhxXc*FAvbGUyKRPEz#q%#bm=>j>ji2I&HY ziqW}Bp?1)IjYX|=WAyW^W-p1EH@qZE<`pt;vcOnGJLQPx#3uV`xf_f%a<$LXlkG~5 zMWLX@V3)F9NUAYsLuYo<6a65wbBw^A-Z=x|D$}cTMuvRP>YR}oJ6}fBap^hr@dR3a z6vyd9TE1yliSrjx0*r2|7%c4+eGa7`A*ScPvKIJ%Nb6~? z&5<2Mn@gbZ(Oq9~;>B_CSJru<2!}71OqpMRd}_+vKZZ=zLX}n}htK@BY2VBoSJou3 zx7Pe5OGUA}1xidFvH9qjA}_KOn1M3H;Vh`_Foa zSg%UN0*UC$4}0X=>qr^8s3QS zf(ecV&HeteoN%7jPk0$FF*#RqnFpo;Hla9+G}4igf#?_IXpKj|I2%2CiqDLUi!Lv5 zbN<%?lgJ%bwi0Q4lGAjSZDc97ls^iO6qrvStX417tX&`vyau7LkH>VURD(e_m={Yi z7kvM(@#CUB91hX6G8sZL#|K!%q10>Q5r$4r@G7{+1x#VjrV|E+(8XNB2G6UUyp7dn zM)%vyx_ac|1-Z}R&y?Ss?zf9_Q6XOK&kiV2yy@wFyC9UyjlR|XVa_*;s3P2!nU!K@ z9m{ym>NIGmR%k_0Gj`7oDiX{1#5iT^C&qlQ4Q{y4%lgD9$bUz9F6;ipxNJkCQ3Ak~ z-q&=P^DWzs=0pkcn?Etiwu#2(Cn2M3b13fTc*vXdNkwur^vV7Fk!` z0OLET8(3yvv|eJTHqqwEJ^gGrDL7foR@X@T>)D3halZ^%_MaZTl}S%&=N)fo4kcCa zK#u^*A4PB$mPXC5P)mEwq2BpJy$gnV7iq6`Pb>LKmW-U|V2s{79wPqkJ&=lvyD3n% zfBO7rGhU%i!`D%ve!E4sgc3`y{ZZ1K^gYBqG6`8wW~E{tl$g9hG3v=zNpq*feDd9a zG-Fd~ZjzWC#2^q+)K9fb#YQfMBi;4sZ@rJl$+BYpoQ?c1-AdqGQ;~q^t;Md$nCPt~ z*eq$iwG88Dt+$@)(uW71riK!A6(vVmPinb*z}0z-4|pDbJQfjlL59A@Im{fCp7SD(mf4ZP4{a8Jb3_k3cy!5z>hk>|2qg6#=0!J z>$s|-rL`Pm&@$BmoKir->R$ubh?CVgZV|^+9IfJ*gkyy`PQ-DKIL6|5OdMRi&i{ir zhT$m3fnru!VY0~qn((b4x=w;%EB^Hxsa2CFo>fu0myx1O z(Qll$)(4Tcs}KYY(yN4Tk@-DkfHq{%ZU z@00Wfh5kOfi!dQSN@6sG3m5ZpA-OCfB^??^N%sQF3G$heG(NeEPqLi+vpL~Hf58{+ zyK?C$Zp(H#``T*(PmWNxA`aT~rEJzGZW=8^$<(_C)TR0SPWdUpK>CCS0>;4W#SZ7^ z2&ho_3s?u$wSd>{k7z%^S<*ODJYb^4HXqW;`D6YMq*;E?)Ke8pF(!=Q*peKshXnQq z^W2f?{oYg3IkOwz3sTog!r!c9_Abv^7+AZIeu1G3qSJxCWd3y$dqpZ+$hF25>DFbY zeGmK?+<+?Jd?_Ft==W|pq*3S0phV6MsDf-I;@&dJszwUxA1J3+7Za4Y&zveM`=E|+ zfvE_cV`eWZGGnz(ym6AX{-s&h6ojm<10Ptgb@#I{_8lK8?Tww*@1310`Ua<1so%e( z7hjA^{mR_UikGMm3Uwb+QfVtacE8#MZYQ&WE_g{!6H;A}wo1e5#hWv7m*Dx>eG8A`Elo8na<yQ+i)b34Q1mT~@~7zsLZLRL{|_BMT9D;Rd?HY~57i-V zpk?wCLRsE9v`x)B8MMD+o#1e-?IA(8bZ?-oO9-K81sq4aOHMte^?L!*9XCLT{Hpa? zf7nw@Mof!h7K-L-D`wW_JVu3=ZL`)}3< zr5l)Zx-;hVq5k8mQ+@Flpw+1iBb$aXxUf>^R*IlIOLy`bO1G++-w|vKo|4W2S=%&A zIwHu*D;-f1iq)p(=jH<+bRS}N9%ObtCoEzpmJ5P6sv@_`=w0)eni;3)MN~EVF#*fw z{*iO_NpGYv`$${$T783-njz2J_&6s$;{2;Tn#P!`N-;VuX=s^)`%`o9O6DL-$0R1= zFQD=x2~!52_Sqler~T4XRTiAJ zdaCw;gCu!QgXpQcmmYVBzvw|JdR}97>t*N9;9&P7Mjb-Q<>E!Z_lD0>>%v}nkz~^# z!YmVBO-oICmP#&{a@rs%FUNO*2FeJ36M)PJ;`Z7mG3UWWDW~%X6KB==LYg-Wbbf#w*83?z2Ho06;pR1jLoPQ9=)KoLU@uL(wedXz)p|nc|5V1*v@1II2=lT9(mL?Jj(#G z|IA*ONdf*w=g%l}R>Rx1?k_^6FEGZO8;-AWjszRoc{|aN*;@C@rO!r}vul@e=)l<8 z`T+D>$L|l8WrXL{#jih=w!`Xxt!)|H28CeGZE51xM|Z7z6T+bqdWYuUh}I6qdBrS( z4DQWNzOl(_%nUd02RdZ!D($1En)Fn|#@3L1J2a4|zmSC5XDDp^#(ko=-GAUMPMCO7 z<(!WGa5>v$7q^<>M2+_EP@z;6u(_|Mx6vs|m}&~VSNd#{bJ6!yl59y5zEaO(A8k#0 zFFOwULYKTWMvvaSpDSdqa@Hb-PS6wz6f=b_e9D1(?-2zh_g+l*tJ0KZt`N$r&EYw2j gw7`90RD}HQQN$lHYy@3+)$3Q`ueDrKoOQZd5 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�coi3G7`wTtR9y1tGFsWDo)HquJc%Ut_QlNzldmd83LfN`gwEA2wD`Hw%KY zuf>Xj>9&6Nw3!XVcD$%L3@eJiEyB@2u!?ml32?odONncP+d$LfNQwquJLsY-oJ^_{ zn^B}#CL0<0X(~-d)gesGPc$!J^@U|t4H}x%T5l|zUXWCwa8gXi^-S>WW8c10 zeQVRRAcj3WD`194SUj`9u6I#gn;b9V=Zr@)8gx6@MGLl3KKx)tPyuJ*FlVfVehT3X zDVvT&?c$7QE2J%AV5Y5SMGwveO_np+vfO|Yh^g7Am{}0OerO=n)#T#d3TpyR^SSHX zHT7vV{x#r!>_;7`$X~}XnhHYgt=I&Haw^os{kaV9w6<;q!(3x34~a!XTq!>Cu%in) zfRrS|BHy4D1Fe&wU2=89c8U|jp9WI9kok!p8>CP+sUVm%Fi9vAD_+qXTE}jcpH%QD z{aP@25D+Wm#R^2pfgpM0A0agZc@`R#1!z}Vgt?l6(5H0;X&Fjp5L}c><%aUcnqgRMbn#1r==#;vlX$=cRs26!))?JAYg(?N?m`70V1 zq=fzs2Qfk+NHKfiObjwR!d?qmo2ZqQ$u2Fh>6mU(2c#{oUbc|uN-x|r-4@l=aVZT7 zh;|M~tp4J#i>OO@WytGMG^G%{j>yXoicBdq1v^w`DftnbSDr~F4einD8HFk!v27(# zKr6x{_tG1@r^1Af_z^3 zI)Wo{RSOp@)m)L)ZHAY6&)RKL14qh6s{oZ>7i|-+3lc}GW0qys)vc^qw%k)m5@TUk z*Gl%rNV5wU9Dhx;)u>0a23jDWLn8{YW9+*oC#XrvUUO2Z%fgA1**mMk|wCtDtZK`*qa&BJ+yDmrO+b_bS#qCB?H%>|fAs1ve7! zIEIburM74YM|;|vK@uIgBy9!FSlYQ{dzW{5l-Q6`E(&ALE=lIi-J>Qo>MY%KDk^qL zM@>-iOR)`58>dv;QIZ;Iasiig*v3*(Rl2$y$s|FyYU#&Aenuf|ZidfSZDe6QpGulZc?P1Mc z%IL=y6KVUBWN%0)VF07?msC=bI0hzN%V5<7AtI>RGv*(6gIYt0cwmK7wyw%h1`M0L zq2$48X0?AURv)r}5v?SvT`^Ts_F-D_s6d^5B@JQ!o^}(|kVF^wmC}%QLXq_BS!-=e z5ky8bwMw$qg>d<-=#*nyD~n)Id@2JkMX-tv*r<#5moA<=clKP8;~+rWr_+(hfD*O$ zCN12kKiN`hv<|JASwIu6Hsn*>4dc;TUw*DZAJ#SNbTlKazM{Sg4$P50KWeQKM1?=P z)Xq7z1k0mZ4JI4vEG;>Q$cPPLGv1Jri5y1^2s&A4z+0exvG!O!iDS8b} zPN@@TT>+}P7HNp;@bhiEjW8O8ER0}XgeFZR;}I^rw7lK;2u&F*TvMo)T`*TFB6B4k z{T7EWHb|3d=@D3~N|K7`FTwlO;t4CY04*bi4jo>)5*$OcTJMHt6*wI(W9Q`Su>BAc%fF3=j-&6 zn?4YY(k|piqFF#2J7C|p%dDuxG>FY1k^xoIg$-;B+ZAt?xs%aQzyc_iycpvyX46vL zWdGShn2M1zGtisOVzNL+HP}~xyhS_vYKFBYd4uDz*_IkK)E*7)h1DL_APuzHt_q>D zu+6g3Kv-FiD{;^+4~pDGmRNQ%(128KqrVjT)zY}^l^b|Z*3{`q(!D+yq4jpDt5jO< zP%O0xY8{LqK4gzf989KX(i))0QZ%nAsTkHV0M!%LII`^Tnc=3G(0EQ@1w2UX;;{2EWoi9A%$R*L)2ArL!&JK z6N!>b^JseV`Wjc5(@i&a zcd#n}TL8<2_gW#H8p9&VKvLzt1cq|Ziq}_ERbek75bTU#h#)O<X@@#eiBdj5yhc zNLW`(MN&?w`B3P|YA?&k4xGfE;EWA33SdW!Q(hRBGcFuayp~qbmE)4Da*td{uohU* zsN-Bjf3_i_GR%87y@J|j`OE@r z_S@O2OyX6Q20T!E5+h`*L%gU-EN|YuzfkjWTL@z3k_3d-8#+SF`Gnuw!=;*I8a-IK zkPb@L7_y>J@`=Z}dI7J3v=L|pJvHSUv^#=r=f9oig`a@u{Om%9@gW}}{+dFa9gjuNqY8obGHL9PYcwxmT#i1OO8kAZ{>$DX;RMTNx zj0GLerfo%7@MB9LNkfp8unz)f7GBCCH?;w=c}l=)MuX0x@i|-GZdpoOEEIs8af%R^ zxzjET>HJ~S*R0w|m4|lpvG+vWgHqPXQ_ zZ`p9Y>!?Xl8qp3ckw~Fb9V8=p3M&nze4u@%U|`l^+LTLMw5+<)Q>O&^T3WrJ8(?IB zs?tct*yE5A3r*Xuw(`_Io(I-e^=Yt0Y4?LQ!SG69g?nYF-!tJ9Q5rhq> z@z4>35sSZVEgNJc!`FHm+zsyPrNhEEg|Pk(a_E%wiLF+$->@h_&|+5@bFPkNE0;Hj zgH~bFLqX~($ne-=4o_Jo%lBDBJiMlLgDq(N;;LbZf~T!+?T}j7(?#6oEDcUtJr<@-<#r+|{%JWsY-<+=e_lo;RqxgR+?g@wgY>A)I zsnWYZP^Yi`b^Kh1{|}{n#o~U&DE>PBRpPI|C#dV$B<{U}`ul|1e~Y+(S5SYSQ2Xx? z_dg2e&03_r7!Qj3NjIo^I{i&?zf#bU_SK05#JzVE|IOn5Q^A`Z`45TvdxBGbtM1d$ z>1TAQa?Kai-(%GNi^aW3hjXOAR@`qC+kVf#r*-nPaO4oSlpix)b)GjMnyjd#Qj5u|LmKUe@2fg zSDB!We@Nnc#l73%zf|IH7Wc;-{^#AK(%&!cZ#(=065m*_@^=a9{O3vhQgQb={BM%@ ztHgba;3`M^-y`lrfFED*agPeFaMZ6?#}h1dl<$DJpM0~*C*l}?dE$P(;50}4UUC1; z=lJgy{}xC2_KEv3!JgvO`6=TTRjxciO&`Ay`n*`&>m2@PYx)rPy9D=1`{kcg&Pi<- z_ZI|v9sTo~xPSUN{^K{Oa`rglXN&v1QT)B)9ul16Xy47^{+N#MXrBY(eoWACv~TvU zs$BC0o&H{N?-ukr^4}`%PYcEz^mkC)vwBrN<&OAM#JxeV(&4{K+`l7eI@)hY+}{v9 z$Kh{mRQaASIOHf_p}1EGb~@T;g}C1)c+gS4uZsJB3V!17-zn~|3F_}jYdU>P+{b@O zm2=bi3bp?!;(nIkD#!RM7WXxR)eip9DeiX*-s7nMc5#1E&^f*|{rpY*b-CJl6#qUf z?yf#n?xT+Smy3Iw;8sWZd&T`>!E+q(_lf%_f~y?uGj@~8H%HKL)W2BVZxPIP^zT-2 z-zm7lk^WwBj|+ayF}@Cq`{}o-a%^?@pC#_I1y6RQUoP(LqxkoV`~8A>kgt`he) z!JUr$qvAdw*x<;2o4Ef}(C6?U68F~yb-sFjJ1Xu|x2XDQ|L@ECq*&a24*zxHze?Qu z9R7>N|4woLp~L^+S>>G7PH}(9;lEz|4~u)&Kx+Axh`%ZBZb7eO{CdT`Rq(Kb{yN3| z+k(}O_TMS)2L-n|;{Q$D)9+N}D}Ok(|EGw1nc&He^xfiqo#14Lf0MXx5cE3wceA)Z zB6!d-KKF|IJAyAe{Ev$J1%uMQj{J+o{d&Ozj`%&|{(Zrt4*y?@`+mX04*%!H{eJ{C z{mqpA&A3aI>r%m(BfeMMuNR!+XrCT&zfVx7KThQTpNjh)hyPbazB?f9M+Nsf^3V7e zRld1`Lk|B6alcCNpo4x|#Jyi|rz8G7;{KxG4u}6?anIeV%8~7;|15E@7ToTLze3!< zBzTX*f2+9f7S!#xbV@lt?i2ShUl#h20NLW7Def0I{JSTs_<7>K)ZxEL{8xzkO%DGr zi~nYE|B1u@+u}bY?r%B#e=Pn-#r@o`q?UiD_+KdQZioL~@%M>)#NmHH{Cmay2M&K- zz8&KJtixaDe^9%BHMM*(Nk2o}XE^+iihqH)FLC&1i2iVexZm#Z-z@%H#r; zB<`;{{8xy-)<2#Hx;O{_2+wukNN!~NXV8IngZ4v>ie@9GwucsXGBF}nAFYXD=I8M%Snzzl4H(mjcJbiw5clmF6XhxqAu z)gF5s1VbyRNVYhL0wVKO8x_{QX32BoY$0iH2yDT@>H;O4gUc1?(glmi5r;zBXpc>u zSz1Yr#oq@|r;2c&4UPrlG7PjRS;7{;$RFoqJ5gh4QToBuip9#;eJ9dj;&)S z7Iq zIw503aa*x!(IzfXEq0WHJ=m@$LlzJTcFqE18;sM>d_A=^kFki1R&&t;%0Pz9s%cLj z)X(Q>t%kIP;~VkRwPHwETV&~rE@6B`1c$@XY)+;uCi!weHonpqdFDHEu-Z=#wDmBg z33bw?nIP?TIBnmCYxg=r^bjRl3Fv@(qG?D=F11SrfKRBLiJE zsJZ2n^vFHH(LXm5a^zu;Mx#MZr|IbGF0+=7@-f5DKjwIKO@qmn+Q^U z^oSkit351}pW$)EFm+f|w(loqob;)jNEt80--&2HYQsr}kKHO#+!nDL>X&R68K?AJ z_$C|M0Cgob2?Jq!X8kGj_y->S8{my$mLFbbA=>@N?g)Tg7mdyfRY5g~YhapUkmT%W7x! zhKpf zFmNLq5=bU%U9rTMfF$P#YTqh8>C=VNnqY?xGfC^JbRJh2`TUgx;>t4U(fc%Sx$IYB zlebvoB=4kxWhLEepUj#nPdBON25{X5-LI_cH{4d6&CkJIxy$IjIKE#}hu28+Kao98 z{w1^LI46G(UGb~9u`*Sq>6>rPqLCaX!xBC6xMTE1CMOWjb`HfJ6>|!#>u_qH%1lOx zJaRy`%F10vj_OaHHPIUwSF)YxP6&THCi7OmiZB8_h5a3B7L=5c01XEmU|esdMB)oB z6#*s5uL0#kPf_<#1IXcDy6n<J+~6L_G->9InL8TbyMsUvMRL(b3(T2C%A!$VUFVE}k7JN@Z5Q1^#ZnSa zpSU}Pf@8c;OB0=y4LYhieeQ|!3a0#$E76iS%}A}Gd| z0r405&%muPME@AJMn!5BaMXmjra415`wXk=*V8al!U6g|*$?e>M}Xhf5p3;>z!eR+ zzumoJ<%$LzMZ3yF?<=d9E%TuixYr35dElyanGkp}(|bEzp+@Ffpd<)pxDqpIa6V!? z7VtecL94pQ;j6W=a2b$+|!z*)(XEhnPkVx)6;@LruOVBS(2q0l~gEM0D zMVz>s1>Kr@-f&j&oKi}XCLQW?9b+c-TGwhG5u=O;=k*NM_*sP7<3zo>T!>Odo#}?q zZcLZBc0qa;{oWPjnU303WluARnh<4*;QVthE}Yezo68rt7g%3@Ao|32YtZGmcOcZQ zW?71c-X%gns^J#c0j@p4`;zG751HKM%gsr+XW}2YXUX0V;dcHU2v3W2!?8Lmms&Kc zSZew>g@Ak8>2pB(q9bR4^6H5ezy-k)<+?j6D-KkUEQJBz<>zXdG}`G5u#Z2Ov8i8y|PuSR<~GQEWNM5QKw68Uz>A-jtK{CPl1J;xZf93v&d6!GSg%**+uN z(?FZrf1VB&0Dbw#?j3patLf$e8&16&@if=pTe}gP>3@ zCA(rCr@LH0lA4@oxrwSXm9}>BMRDK>C^t_=)-b{4Q0Q)&QQ=3|%hL=&lW{xlnu*|F zunQ@=SHlOH;gKZ-w;NX~8nvY1s>NrY_)aH?*4!e}-Z(GO+63OEa`LS(e6>cyA_HGr zR%#ARBUddlJaxm!mt`?JRzAtN-HKsJdH69#=?)p#TkKhMOb1JKUh&je?;d)`D?w;|80HI zNae-KD~M$pQ7bcd68*K9$16%paMQKM{f8so1L$?M60P2>6K(ui2e}4tl=vgHo656F zDRg8OlBH(>cRCB;)G?%IQaz-Oge6Ox>|vq@E{Ng}7*MRdEH1TuGlafrr#=s|q|o+V z5KctKTk?!4O{2U_v=v5Ak)-rPA##gLSI|F7ASoBhx?gU10&Q8-71q`ZjfEa}afwk| zUspT}_w3U%{|^0D>DT`*_<^9iv9gMn0qVi$Gt@)F3`*eG0%L<<)-!lR*nrBUd{ zs}^F7<>bV_DMKp#vFg=0U*5I*OnILzI7hHd@G^&gh2ve4wLr)BjuO8{+;w^MyUyQA zNFH>0qm^g*36^KH_|);3mcUh;=+rT1(8xl>QrrXrOi<)@e; zMyMrhYT?YPn6o>WTt03FM`)}%Osv6H@b``I>8k){%m1h512x0eSJSLqQB9L9 zrdmw6_23h3nrACKw8wzk-Eb`cU8|dHOl}sN0Eq^}Vgfr+^Ki)0WK_4>(Zqsj-nx~S zT9(f9;UZZqBj^K3IA_EQ5nJM{(b0?Z{kcSA#6)xyRGPyIZ|>1t7_lqxn&R7$35w+bmh zC6yasRj;LYua%Eg*y857>hd8VblL6;%X2m4nYmU#>QJc~a zJ-}a>Q1c+Af{!QA0kG9U=q#azvcA2F7|?bm6}*sYSOo-f58&EWx*oqLN#|Ik)ypCr zUnc6}k7wfG5Ts!6uge%t{5yT;p#;Cu5;vT0zmq zC^p>&>HKUMkqK*0uq4MizY?5C)tczZ@fkmsJouAckTYq=MOZX1>(pJu6nDgU!Y1jQ zw*)uySo?x?UkMQsc4tkMv(?_JT(TD#hMEEwzv9a#$ve1Jdx-u>$wG3fnfk5{+6o#c zR7HKuy}Cx)66xtXW3&-R*U9n?h4y&KVE~yP)cud9VM@G>TqmpzH0w7hlEL%22_p;{DG7bwt zxw%E)uAi?h)_P1y?(@~fwsx@gA6Q>3DRk04ic&~_w%T|U{n)7b;$i8_ko`Qhu}xob zJ>SG=W9)j?9#v_9t{K+vBg(P59i;)I)((l*2g?o=%fgUG=wp0zEu@`$sof3v=wL4R z1kOs5W>)=#FOz;^+Jvtuv&xjD zY|@YDk|ujryDV3V3Y$K|D90?Sek5P1#MO#aFFCHtszc_pMn>D1ZP$xI3eNNek=AB~N5W*&ZXiaj8!MwoSr4}~} zB(G|*zWW2_EK#8EmmOBeHRG~ko@B7xg?r;=JHwV7Zx@Q#2Q!t+Ov9tDvVoX|+qCFQ z0(23Mnh02~U}i~Dm@O5q8LdNTKS|FThM~ff8-=e~Sf^$gN5o`!YaQSS!BJ6ClCk_M zUnQtY)P>3B;gS%|u>6EsjtoT9MIo9i+rCR@`Ek-vw6BGXjeQ~c2A)-(WH{@Cb!y)c zJ37!c7nP`jSmBkN%VMz=L2XU13F0$*5zI!U_R^h z5kXN=LAi>Gio*5c6%mor-{-rB=RD`hIVs-Xz3=hP($@U)MDEPEUmU9?5c|Dnin&|pu<*nc+Ha7QwPjB zN49O#lxY6Rsmue0+4k+&m%B%83MaYncH{fYn)Xb37P^hg`^TsgdgR>3fS%MoxMN8Rc8 z*<$zd(GselR=2b@sNA!2lCEeNgjIXp6d9vpHf`M6$;!x%)cUS&9%m41zpDjT`6+l? zcGXU|ma?mjEx_qr$h-@1a?eH+GfV5kip=;VYX#4)Z&#xGn_S<{E)dNkZG~s{-`B_c zZ{Y#&xPjg`x2>ZtK#UnINg1ORg?O>+*4dX zr}U+J(gUu4V}Ua4=VX$nm%f!h)j5)f@@ty^K^wuCo$iqbwwp9#>Py>EEw7ZK_sOy@ z^ZhQFs7l&599@e1*iMrecRLX3tbJ*&?d<7eVO^IYwU3Ol>>j9%ed9Yv#&)Dox6ex^VqgEKDsV7jqELSnvD!Rl#>UTS!>ihN}tX1+QE$(Gt!T6c^R7yMB)~@#!%Dt+K z#IU~$IUJsE%p0D2eqa0FpWk;C@Bkn_4hO@p4#!9?^3l=JYVwYie7!A8XFP z#?@2C@9Dz5`vhAH_U`*25O|B>=U{dSSnWOd#w^3s^-K#;k!b?Zt*dVdb<^GpRlzXc-8E%OOjZR}&95+-JD@7u4M{A*XX-NM z{vs$7ueXGlshSnWUYAtCjZZA8`iq=gQ0|H*qid4P%KB}ioq%mhEEDR7XzP%x#O)weuBfQ?%Nu3c{n+xd6s>ig{asO( zV$?>TL?e<;wa^07jm7QDG;hCW3z;9aGg0ZO@==o1C4Kf|noyqXx^x=)C-(8A5Mixo z=4879jp?#o`!$T1e;Nx#ixB7%rekJ3gWN;vq?PY_e3qaNGAmurK|Ecw@+^ZT{9=~D zvah>$PDZPx1n37nPHj2(mw!)L?i%D#NU9$?sslQ}NX;p>jjq%y$J{!2ELj*Z*3XXK z5G%v>lPv<3zjNkBF!az_8Y}SvRY(0#3P7y{AlJAhl)vwlQglcW8NW$G@$X|y{qRkS@j40ZVVcY@voTx6O_{LqTmuAPWL4hWUBMD z#2ga|J1!N{Dv@q+XQkVGi5q#Xb!)K3K9?HFoQ8dj|1)2$1O#8@0#o$b?U(YiHmr?V zwZfFwDq8Yu2QF3%sEZ~23-EUK?^^faCuOV__^U^KretMrJP)0}wr+c?u3(iSUkB0Mjn?|LTGo}(&SQPV>C?xv zPWrA9kM>X=f5@wxv68+KrwchmFgb3qTtG-{DwEzg*%|P%lO05sS&HF?1`B?^rX)^` zx?>4h@ya+-%yJr)3|K9xoUx9bw6H>((WlBNPm$%ef4R8Vo}qN|5tL|N-1*p8?THs} z>~kg^QtOHt1pCtQiha)5mqAv#gNwWwQfX6};Ox> zhbvMIJgm^ig)?)k!gezmkq!B$`o%JU29ZqUUwjQaPcJBUgq4M?Al=Fq!zdkE^AAFV zSqCw+%U=C#@=1_;bqW@L; zc5>;gOIcNBbd*`YpgUQbcbF5Vl3AX{82P_lrzvazOa0)cswy__#(ybrEaVkEURFBrKWc2vgLIv zR<1hf$aSG{sr3yb_}H_cJ6-lzr5`o?|j#LuD$-ok9_pzPk!o_ zPk*L8zxikP-1@oCuW#Af{hHUkfAi^Qt=|aA>ysm+7mdB<&2M?@>n^+QgCBbTZC|+k zU+?(hm)`y5JHPVPuie%9hOd9)n|J@)x4wPPckcb}*;n4z`iA?z_x%Tc@WUT1T=Zb- z^o^HYam9~+vY@1V$xk2p_`^T@`6K`Si(fwat6%@-vETmg_mBVKk54@LAAkDKr~dq3 ze|h?^&;0G#zd!en=l8y_&&prOK6?I|iptuG*ygP3EtmpP^14x60~bF`wxuAIBgcnL zS}EDNQHz$9Ls5A&PXt%+QH+Us_(q()_%ObLp$0^j|Z19ndCllL}jxZO#|cUzQJwIUiek{an){UdKueS zRWZDemcC`~&O0P4h&vuIF`S+ll|p-8Z)TH%5*p1Y^4N^XjLb1}6YWk7FXQfMeOMW# z+s#;J7i>W9Fsg?7wA(qL#3-_rC zER!F>UW8T3jqrsq+<6xly|{1%bE_9` zTb9XgB_cLm9Q_<7vt!;$YJ(Rp`)RL~{M~GcpdX_dSU7GJ#l`xSaHR&(wH&U{y>TcI zl}EST23A+TJXsv3g%-xD=Qa2}VmEs53jQp*McO>}l!;xQli${wRRZNmDw{ZHEV_Zm zXg{xHsfsx*CNGLH>n>*YgKajQV~(yR9LDS=!ij9g8+2#gj{oGhmA<-=+|1Tn$UR$| zT)l}2Af**@Gv=SN-TJpI_?J1%6gwFi){7^LxIGazS+?!x90D8ro+YUEyN_2uwtQuf zFTufLEIkXFIlJsKsqErZ)sh<26j%ykNTEw)s#dJ5u3O1tl3etUAnJE|2pf;Kyd<+-;#XXzRdn`*WP{q3H${R{r4E?Tj1mL7^CY0%-}$`BDgZe ziw$c0or)G?VVyK|uiP1(Q_^#AOhpaNb)V_(v(MjwtxE}c`T{xowpL|`V5KDf(N+9Xxo3uVMp6+qR~0WyJe951vSN#43C_vCUQ0QoFWSxpdM7d)^6H8@lxDg^#C5SITTa z^jUWc`s#WQSc>L@x*~u$Ww9=52^Xo-iDBM}(!<;I&WnSKVaC(Ax0X3+3D>9rqxa96 zRwYeSr7?e^^d5)npaLUNYK5o^5=$+Z#6#obBoZTVW0|Ayc4 zOm0)5kl`DAdE5Qo)N1uKX&oU)F=5AQ9r*UlhBz}iwqdn27IeLh`e^T1*5k4`De`;> z@<9~-n<V1u!#^{je@ps?1gZ*FMk5JFmN2ffCm`ni3A*8#g&NgHEoH%?wM!g9ket*T6L z(!P=kpEB;3B@eskao?kR_Z9wX@4f>8;U9ZmwBQ#BK70@O_X+c-68zucqr*}H>`yz>0c$=)_j@*1A-H-Z`)e6!MRvIe!XDnA+Jn}N zdu?5mANuPV(zsKXM_|ykO}T|^E-9(38RQ6!UL@!FSwaeecR-Up#i5Ywx)JXqv+>52 zCFlT6un*CjYIk?|=qiTwXa?~m%NN%iSZQBrqSc@=Dc;$g8&^czG@ACsW#eh}YeHal z)j>l|ZDDdt6T{`fKKGQI(i6fP*c(vD2*9WfdxO743jsOu0R0XHUeL0lVjN$tax*)i(_jnbND~*-FGqY7T`m`=YVekKLh>* z6h5(c-;uxxz$rj0unl+>@OI$Cz~_Oxfmb~F;^D{G{~b_*s5Q8UM!BNIU)+*a9jLBW zVC&bnHq|$tlWN_(aU-`zKCh>dY{<7C5PM_Weq(1l8%9 zNzip^;{X@k4HBOJv+X~3%nj|f%|C;(%8KOpfL_rmBIMeK-pHC;=1hi71mk>n@se5B zw7$jk_YIC3-wlM-bBZ*GHVudxh7g}%qnpM7`S*I&K~q}y4m#_>p0|M2Y(JCsa+`8~ z&@b26p}bwrCuEMcyGF@-i-K>N{lD0pUEivbSsBDk5@>e>a1&h}@YiVz`GFQ*38s1P;+rYMiTEZ;Xkz87VR%w%9?LJ~}B@Yp|1Z_%N_4PC=AxJ~G zwRKbLeARdE9Z<`g@RyZCcTuX{LsoaLtqUo`^>^7NJeRvR&qDLPp=+2{(_Zejq-vQ; zteJB-;{)4NV*bnK;em@L`c@hnOY}O?=CuC0-k8*cjTWmPW3Rp~S1QV##sKwj>((V( zOL|sXtl&#}S8|qiKNvwFMi_0cXkS@JLcp7X^t36>1ZrqAqWdm$pSV<1%Z$ z^utytHnj!$BfBLGD^na5MKhKB&6Xt6N9S%6?CiDK@qZqBXKz#zxDjA&-?*c^gj$s> zI`>}pRb3K2g-vj38>@Hru)bu;ooz-En1zj@h&ex%@oTkJo;7P&&~)DT4=A46m)ZSS z-c3Y;?7mziH>AQV=Hj6{8?79d&gyVlHO7gelGy1Kc#)Sa-1C~Y>1eZK!jtV_a9usy zd&A?F2?Cti@U!dPx-&06#3Tn5VdqQgWHii?Tu#L6Bd4$ZJMbt_M-`a5z?-Y&mxd53 z5ofomth%hysLgUsRv9gY6&>qK#vfaT+O0$edq;MrQybe+Qmd@2*wW&xJCEF8C#rYq zKJryN*N3D>%TMX)?e1jtF?EVPJOR;1=_md1j!N6^e$W zT+qPXIl3~p-HjumLr+z&&3h+_Q#LT!?Mqk2l^+X+@`<%C+<_=-`O>N8#f6?|WMWbB zp2u4m6(WC4R)ajWV=h0_<)(bx|y_vk=$kV~74$2R#|t zXV=QiO%vppHIn3Ve1ay-`NVfAafS?a(KEdRUE7C|X+%xOuQk@ANl!nCi%qFP)0=CW2L0BKKNf5>gtF_Jy#t(YcPsHaUxeR& znrKSx5MXi2*joZ4#u4$4#^;(=LG3jpCFwSspfmb*+X*JOV>7WVOSSno!`*FhcD_fl z8^{!EjkY3nrp|MabE4%)|59tlCTy;0IY1e;%_@_#kg!7|ieQbKH~gOyGTYKyjS{@j zzFb*~9kfBw$?RevE+d~eui)wpMY#HRuy%`SUFB~L{3lDx_-xCMDuy3)) zqAvs}1zKf$C5vV#iYvW#kK~KOYErF1T*p(bcGYDAx9pMk?(;fk860Ei^K9rDTlKDd znmc1OF;Fu=SIz833rTck)R>-evwE;=ct@&1cNK7(o1I~~9-JxB=3M&4|x1Bj~Qm_h;KOs<=ird~$zF9l{Xz@^bb}%q zHs;7Y2{_8$0V(;`wemD3#JfNRsj=6tis(j_?d+JFLf6fp%p2mX2oxD4VPKT2VrbU< zjL3<$+cYRDz&rOLh*(jfx<@)nv_)dIz)k3isjE(fg8t7fc-ZA;-IAhdrbOJ`NN8o; z6c=RKr$cr{qyW^5b*o=?DYVPk#f4e+op+d&;STYmdwi8_vwE<^W(vx+wk(T_^0{Le zua3zsP|{y3%`8r87w z%c@LG_2SfdRKZcsU-Wk1|ABezSLLxkJCFV9Joe{wP3#&N9MDCke&NSP2kV^Dtl~oC ztmv3&+439duG3;L!qh;Qp_=xO)K^bem2$IA?odsUCffpfeG6N^XPC@s@MJ~f-ef-& zHD+dZxCnh_#WUVd&CISZ+4i(BT+VtnAo=HQHktl7m z3SF0$_*xV?`Na2P>D~DJ!w>Cm9$;Mmi@-*&0Wfy(CrE87jXAKu^Qf+bRXR#F)*(85 zlh6YkSeUWj7cDRM_HoreOF0ZeVlAM8m|YP2Kp1|=K+|v6mAY6#=9^bwk_`F3*@fBe zCRq$4hG4%a+rGk~+99F%PTTAbxD%gZzDM&Kd+U|1M%G6SNnN2R-qURVYS+K*6lkQ! zw)g6?tU5!?8#8;lQzldE8-3T11%GBtju!(^&RwGcqzs#x$J`H=X3xqtXC2D+1MbWN zaum{kK(@V6ja`s38!;}Q;tCqu?$16jcm1!;Q~wY0LKH?XT8m=AiG2bMaG;SCHUmhI z>u=Z@Fb5UHZTLcYLO%q_5tCR2J$p>J0| z{kjGoa|>Pd3wkeVDt$`K+@h!z%y?x740CM@66R4EL#j@86>!6Rg)?oiyssz4lAFsW z+w?d&o;$r~fP}KTbcxLXLY~umW z#RfB%j=5QB&R1>CTn62_`eTL%Is4;74OLmgb>?g=VJJmzfmO3J1lLaViNRr0*|SCO zY#we@9AjFB*I17l9eHUEN3>-Y`(zF;jAlIq7$!0?H%0O3N$h*EYa+Skb%K5)YH1Ij zmJGvp61f_0(slUs9QJK`R6WYc^q>oCjxa0*bIv0ghh%zPV~!{o>Z6^}CMu7C`GHEz z!V!g&J|Zc8y@)o@mTt7F#FWIY2ysj!yE55A%G3(%71+qio~tb+*M1r6lT5SfVa3NB zSvc9AQXaC!RsgIXSv1+6{W2*#yuZbh?Z^F%_my&Sl$8&+s$*T+Zksm1$-i%N=(u&P ziL3B>%~4irk}iDvXpSmWXh~C-*UX>nD8{Mlx%eJcl*|5bxQ%_X{o-8qL+gw@R3Nst zRSfa<;aEA0#mhAgj1(>&7-0>?&m9NCMEEExhcWxg7pYFD%kV{-s|b#okt2Q?cQOn` zoK+M@&3w`Bf@s);9W`r8ceY65qBk#RdFL%`GcRXpXY4QGxyLB?y@V)Y^OACqFVE)X z6abZ=nU}My)f?h}vOL|;GiTn~_$13S+kTX%^KzA6ywEe5IWJd{v1&D}6*zOWiLZjW zCgSLV_%}74nbj{_n#LSm7`Ni`+0Hk)_J%>2mej1xqliNn^*3?n%9ucZQH zSK|D7xr@p%CNyP|ek&$SREE>6#`|9W*sKI(T6!1JV+%(8a~bh?WNaN<=%SL_)R>o> z*r?nOxv6af+hVI%3h5Owd8PZat&^%?reSjNd8JZoUw({-7?Q-kf(aWDs(`c;u|+aX zkex5sD~KlX96}ly?tMnR^?xu%LmXJ?TSj?3M2herZWW$Y6$Qh2{ z*uT__yzZoIkl)?RocXcT=67}N(nyN)Bf_j_7BWyzb>Es`O!Lt8~=p7 zQIpT`fhvV!*ZhJYe5`-Ci`e{*_x!?O5*%ur7qv#l95;ELzEtaqTwqdJ$qgm77U8Cv z!x)u|&4NhI-IfqC!uGFOP{6N-!ehJLKA&p^j9CyxKo*>v$t9khHtFkza$msw&2FP9 zn56rtz3ETU7DNUA+(=@Hxcf}8ubKFkBLbsMMDR&o6k4|WLeI*@5 za1Rl=)nme$5kEvV$(pP9u?A}4%*fBQ--68Ybm6SHf0M1mJY0A{>_`hq3f{cn(RWu!r38zGV8ArT|4`BSC46eP29ryOxsMDg$FqsKJF|l zaX{KF&oK~li@#=(Uu9z5)Q!l^q5|EI${g8kUHLaQ_@M+w{bUo^?DXu~k$X|WH?ZYr?(;`~wi7f-W&u+>y%dr^Iph2;;ebq)_}GrbHaZm5%(l$RAmZ3rvXR@*3A8djODb(HU=5v zrrc=q>FS{yJu`K6x1rJtXnPdpW&W1XzogMlV_+ip+DTxcMUQTHEeToTuOeh3vPM6k zbtF~?x~SA_A>yO<;It-MiGO4_r;6?AQD3tO1HE_7$(hOmUqE)?<&Dc(x^p_z1kJWH z;Z+vqw3p?1WLfr$qCHWxyIG^uBZQe?O=Yq7&t*y8fGelvwwEv9%IV&|i%e+Bczk&s>j?$L z$~iub*4$>RDs`3dpM88af3VlGn5u&4ldCO$W!{BbRro3ttyp(+K4}HXnTM@y-Rs&H z&H79=?LAGe)qn6tBR*A`xn6bFJkU#L$e9{5&Jlue>9!lE)uQASRQ-d!rLk{nuB84S z?Jk+61@Un}%(YkDbg46DDf$e-=g1dM*+yC!-r`c${U+H9)2mFdW@+(M_Sif0$_C5K z*g$u;dM)P5w$B14`kXN_b<>@Z@5t!%=%!;9R+w~`y{RdRPIAz=GDw&N zPdiW=MwykYLt2%Z*jJ)S&dGmm3cCzmS(x7*ZBn#vwz4RvJ!uMA1%s8vInDjYNo&z= zvZE%;m-8y)Itx3Mp0q@2%&Gz_ox#K}6LJSwmU@c|PF}7m%wyj#BL|zSimW}2cC_29 z{feFxEx9`(quQ6Iv5ae2m{rB;L2jcD60>UBDD`G6bz#?#Sv6f>5gxmJEj>H*QS6o1 zcw|$$YDUsV_sBS>T_Q`XSl!7^rCG&V8*}l|F4UTcen#m~={z7~7G*`u#;OA&&vLt; zoo$}W51XF{`J#-CtfP^kJ;cB2U~j{Z!o#r9^{=ZA$%;9>D!bVT;i|db6{=!gsFXO( zm6&*}Iy7m)J!pc{!;&WSKTeZv)!|7a9hg*bM3 zMFYMQ&cRNri*%sT?^jT>vh5J|pSZExo`A6W85)6U|L4>&LWvq}`4{Tnewygh*z_6- zZ60r9xi`?XBE-uC!^4YRt{ZdOel^F3hf}3>mHp-0w)Nwx-tBMcR-7q)U+w0b<;Xte zmyuRqJ95RSfsWDEK)UgaV)Y8FljnRlEs3E2_!}rt7 zwVZb0&?h}}M$2cd+lL#aNSJYk-C{;&enDZaoAbuqM20~Hgl68>2J4B>*tJv?Hx|^M znC+2r_|CbGV&->luTTr@aYuf=LGIlQC5jYbtRr*$oFWq+XD#F+8$R~juW63jykO); zi>HS<3wOvU`R?l(zJz0+2ycCMd#KC~?Nw+F-6EGy{|4Hld?yW3rO4FDnd1f$#*KD9 zmN=52Yl{73yTvc{xY4_hYKvDY@*ks~(Iv#jq$S$F^_j>t-L@(;rL@o5fjQGKl_U8< zfULVS3$i{k6mt_6wKe!8`_U9IDge`JGLtK(eAQ-0dtJ2Bj0h0dK6`xDWuIwd@NEt3 zm?nhZ7KV7XzRg8fg%F)LqQE z#uc}1aqI$vZP&Ds;(Ia*VbrP^TKPG*AhPdAFrhTV`h>M+&Ml1Wxps_NHFNmYmmiS zlxFOq!;lWsF?R}g&Z=w0Sv}Nvk9t0^y`S(7oywh#0OWLLIu4t{oxOE9kGsRCa2G8L zMm59^c+?Jb95IDEU-dOE)*+>kHr0fVBd2i3M4okR+leOKb{sW@I|=G=9}TG6;70X2 zY^Ruxc~iNgonTLwyCpJByQ{9IStW2bQEE8WuQ@hV6@g2uc=`OFtcPyCFU0#XrD4Eh-UWxBmIHkKOB;M!GqAA_ymHZCfwv@kI zV}|k3CiQc1%xJPaRSMobfoZ4MJ$?qte(%pU~zoXI)$i-sSzEgX|P2p||LGIx4 z-@M`3E+o%9v1bZ*QwVZL?-cH)5abT_BJ##_3PJ8bg(I)KDFnG=`xNeS336y3`=@f3 zL!g5@_Bry#a|*%kxNr)0QwVm)j;Y+`5bO}o!71Fm2m$Zd_o9|)Ql;Cv2Gf0u&G{^E z?ex3$jPRW%9olR@I_CU>0bO~KF|toa+m&9ORAg@L?uk6Vkis7B<=fO&&o7$FKd&%F z;O7_b$G?nK8`bmco#p|qGAoCy=DHWI&H1Zy8!Ix-eM4>wJ{IQurab0@eSM>f@7mnv zet*tSp3DVYmodcQKCflbWiDo$RIz09&mZQt?SbiH2|KK}T2q$4tX_MHdP_H#J=Z)z>OU3S#vqifPKJByGMM5q?Kp%oDU-;`mtCwwH zc3yb=)*Vb^(;v;PNF#+~%+lfNjTED;<{yhP?xHm3Q*Wek?Y*;C%#;mnhkl!8`mFS)mSu@)TBVSNNuo2Jazq4Ny z*+=0C1s;O7CqZ45NO)xKVr8})3e)aWyCt4bmexD7y&&5ZjXgFvyqSiPFBllE>Ee>27n` zmO~yp)}I-6b5~2cd2DT;>9%WwuxIpLt@~O2gf7Z_epfdu*0Q6&?fLF^U}qzjp62#^ z_d9T+A&32bla*CkR}bY!0Sve;J9oG=hq3|j8z_iQ8fW?Br6O$xqzuo#Xoc9fDYf#F=I{HJ4g*Rpgj{(HmF}4gdxwoSrwpBlfV%1=3JJIg$V^43)?BLv2w2_}N z<+*7tkQy}MQV<`#jITo&h5J9EExU}d8Aj@zdY+4-J=bYhj$^_~evDv`bKiWVzd^y;pbxZ6HcU^~S1i;;k2>CUfvw5r{Ud^4usl?0fi&^18G4v_xmNIs42z z073V2oAf?yF!mxcVlY;a+stmdsXsz_x3_#C8dr=Jz8L#tx6hz4E_I@vAZ6KMXKx_e z&PR>Dw@kc>t=^S36{&~Ps^bDBuL(k7X)>&`3EiI=n}+_VeI?8J72B}omIvJ}Lc5SQ zJzQbn2AjSV(q6AWzW8r@+pfVOU7hZBgN;eEZ?5wKlk6cZ!H$xgF3Mxi7kaul=z05F zW^7;yRo3*hT$Vye>FLN;#pn=s9xG{?XdZ2v8XEk*SJu%$&NSK`amtdj#4aYs+9#l# zN`{6LR*X4bDAg2huTaumZ}ti#O=`Ki#>wUP@!=^c_4C`vT5}@KbZ;3OuZ(uWag;J+ zgK|<&-bL2SiB4{14fM+=>!eh0X5U?4@0w(v0xw~?QMmt?80dkD>Lq%YWAYWyRV(W3 z0~M35fOhV;+wC>n^91%kb9*pxQX6LuXxG_LdkV!~i9BnT?wHMdWK+3kY{|;l*Ci<~ zA|tOjUl6`KJ+^StV9eWgUpaiFyC_yVj|tXZk-3^p>F{{AtVc(@4&X|2l-qP)_U4Vv zTT*V6o3LUtG~}Odxwh$#J^9o?OFzag?6@BKuFmne1U{6ltT@e-q)#>#ed9e9NRS4O z6+Ik))XfQIHElI1q6%xI=1kM}y+wbIrz~vafmVC5Komar9mKr zP z{zc`Ot3!$(pP*aGx|v<0W!+S$amAYY^=(bQvg5uvs5a{LJAc-q%wORW4*P_Y^DZnd zuUD1i@6;dd-S;@K8At=S0vGeH(UocX}fr{)HcA%wCWE zwfFAb_x11X-S^A`*aa@c?OVaCu=f?jcrV?-g?VVj zc2-$&vE(q{IF!lQOe4Y$^+whzH}=meUpFiBq;K~KXPWHcAe64Mmp#ICMu%)E{aNMf zgFBiGl&gkfZZSV$R(Z>;-VwJVV(*ws*h!W8W{Q`7v8?`XmVJiAiLUMcoIeb9>_FC; z2F_i46bUl@D=H+++FYNd$+oSKB)W&i5!7Fl*wWPESx%{&C7?`>C_VGbQsrF$QPyQH zXksxbbC+1~#+2@|*tn^EaalC!uHV?0VK%iMz}qRZ&7E8J9R!lp)iXFNW&he+H#emQ z7~XL|l&&j6Ag$z8SSg3ps~_dQ>_XO(lC{dm96 z@ID=*S0QHFQB6OXKQr%V>>W2KjpYxr=>N!vUgjZP1)0 z^gyO-btbn?xxBIQqP`;_E^0Aaf}B>PsLSG|wxPoy*qka_8UHGshm-u{8i9YpyNrLs z!-IKzwCl_-)=%YI+FrI|f1+}xY4G3eVOA0@bM+Ih-HaNHxC=EV!FpsNlDmOHmWgwgPEGEYbp6JTycwhL za+SwFAqQx|cO`q&jJQVoFSbZ&ne3w$+noG{*W+Beb7fb~c*Jr{@lShM*l(MayPaa1 zY5ORfcUpnaPLtH$wcd4ntgEui(9Fsww{|xE92Uv=LvMsiveu0lQN-T%X8lkr-Nz?= znN>cNr+;@_6Y*;%P)0RJF;x#sI!1Qvw->hM92e95Wq2g1_;1U^HzSd|$<0A_JAyL?b(x`D zlvD|qKkXMm!iUuckA2`SuoghR$o4mXObnj!Mb_Kdz}P3k3SQ{ufYEm3;BSDOmS z%AxSpeOKo5DZ9VT>rwDW-K_p5#VFa!=Jm-Y&!&%Si+rs7gveEXwk6ynezB^bOqSYd zZLrt@m*NxUtELfD*2r+uXe@+p7w2+xV{Xwd&on~e>Oe|5#5^lpopZxX9YZ(8W9`}D zA20O<2eHP>)gwF3=?9w zMe~O!V`xz{w+J13WWhHiq)WRQdGj+e%#)wEXDerXERl|=*VN3;K6kIHAY_r(_u|jy z;+(}_=9xowONuGEZsONHR$XH~{mRiG6l$;h3Ass9VKTE#QaVOA?rs`fbiyL(ETmdk zDc$1YVP|P&s3VMM#?{YBJlZ*}jN2@5*L$U-X|!qP)-a7WC%~e2sqG)>YQfs1arNO;Ps8HIO0#m2#<&e?Zl}|D|O|exhY`eN9RSDl{ zST8-TbjSx*#I(@n|9Fy0_a1XIBX=%x^aOntdYwfjJtRT)t<=X{?9aXiHSr+!XMaF5 zX6Ju{yWrW*7yT-pT_x73Bj)Y7iLv4`k$U5+Bg ztp)L?9p%UV@;55)RcwB}`Za$2GaCHKNA~V_`08+?B*V?5nLwxUU9A;YP^q42-#lZ6 z#hvtUS*x%%84^D$sn}??mj$`Ax6yi&4j_e8B7}q3qLwiW(S5w<4|J;|9xmk4V&~!)Lk08WMabPfNN&m=-$0^%asf}Q63&; zajQ~fm9oK2t&N>I+FyPYbvtD?UHHWV+x!ga$BmE7HkUmPDSsSQX||Eub|uP$L4`f* zu71K6nOhUGBm-D-TffOa?LX;-O27G3ovK<|!TxXc^4RVZyO*`veOfwc_sQ7O@Rv~V zG6=8CT})0{ic3ZMNA$ey#?9;3r^a`Z4DPcG69nJb>gEyEf9-S-J-3HK1+8F%d&v?a z>+SXQYYa{+uxJULn;mUX*(+ilNit!cSNaq$o4(qmN*>#HRn<9?SCu1qX4lR!_urHZ z4o;Yo<@}~hNv%bxlIlTT1@Eyh@0FC-EjfX%$01WvGdNL}(yw!5QYA~4rBkVr+9kE= zl(?#Du#wx$b9|Fh5z41ilUTO_$!pz{6Tt@k(z29Z>E+VaDc);N1`zqr0&$NMf@Qh< z_cBnqB9Tek*tS;LB>fG%_21ixqF+-{SzA$Emt6+hAfLR!IXu!m((A7F3tnbn`9ILx zqw8X<_ciRFk~{pbig{1I)VSQqC&yWy#uz>MUNUBy>@%Jv?g{vkG~0SU#R12hWRZ#= zL&m|L(eS{w+dZyKq<$F@o0Pfoi5fyz*xB9H+v${0OnC56dzVf}g`JN2HO=dr%eplY^%iNq{Usn>n_`IgRSm~?Rrt#mUxdF6EbV!1t9hzG)%UC z8q`G*iD}E)39ye5=I((YyFag07p4fh-^;Fy+Oe^$jN1i6EtW>Z?y=DB+0A9|op)z9 zH2iH-(fhJouE6FFy4F&9-o_XYjms{*YD>dPl-t_t+s)dhb~dierk19SM$gQfvs;_n zo6OqQO=p|7b*;@C&oCRB+uGKP*jT@zseRL?^_w;#HAoip>gKh)6x`I%T)*DRn!K=f z@pnsodwZ*CSl8IxYTBCG+nYBu32)fcDBQ4_fX*@OV|%I%--Em=N?7&Cc~rlCy=iP- zv&NjwYwMOG?>sRQ&MMwX9XXZlL6mrQ0FZk(|A&(?7B8N0)2;@dE8ETF+%M zFK|6cG0R6o;cpwZvuol|w}gAW)jlq-s9dIgV(H|NUGIv`RO#wIrmBN|yQ5p5vePE6 zg1cKyOMUyg$wu~z!gq6Jt~p`kW-|g6Ge#NJ71h~o1UKO4&4)bR8y?o~w0EVNhUj#M zrYgLSh~m}ClnE8qd$7OuJQpi#Ol&y=u{NHsAjzBDPu}|t^*8nR<|-dGHNAX{ghoPPI&JgeVV)^vw!<8usMrg*q8brgzHbp z?cZ-oU)U$JxcR%`XA!pY&(}{S(N<7K_|!p$}j4w^-}S_`OndxXN5} zxMV*O@(-;f&A5FY=Fo5?536!Ca;0{5*RrX3V|!EUV$;ypy#Cxxjaw{XEM?3)>t5LR z!R6WT)dZh3|DWKWS?0prOjrTrZ}^_1lVJvXpZESG&8OlAV(ANE3kNq>dp}?6@whij zd;V(p1?1aD^Mw6gzF&hK!TW(5fR6#U0G|Wy0F=jf1NQldzWY}>xH?Ab+x0R2+t59p9|hmy>F$*;>>Ga(aO24@ z>>E8T8{U}XThh+|7GIGg>|Eaze*F98r)H-i#Bb317q=VC;&}Ecloxho?_}tY0f*q{ znJ2k#JHWDkzQ=nolihFmyi@qUg?|U;CxL_CEuP-R_dfjmZ2~U${5L(mDPb=AGP?-> zzng#?Jzqc^WcH_ox%glvyIZmMc;kzMnX6ye_m+mqb}y>Wd<*7hyhuM^Y}moxcZfrv zb77gESe`n5(UJxIRn;}MOP4LLTTz+aL{~-z^{X2io7QBTQt=}5uI?UG3X|q)pSeO= z&v*L)XjQG2H*)oFRkex589Y17zE7i$OB+2pAdkwH4yZCE)?R>nuVs-ALuDq>u}Jv- zVb_0m02~%862lJ$9$~j6F+h)nEi#1vztzFcJpF}zF9V)xeqrB2@Z%nfPQocUzRAwR z-p%vix!%q%p>0ye%ua){c()nrHaqz}v71%)t`kic82YZaxb>H>d;Cq0|IOojJigcC z`#pZZC3!i<_-yUgbHHRV;yg!J3cR$i@Ne)wKvk$(AJtKV8<&Tih= z+^}imS)J>fS39DSm61-CI)+Es)BNP$D;!q%E~ShZ|*uqFa)4wVaY zvQ|a2X3ZM!#Ccs~y|UTi{H*m}3%eLtziX^t)}HgU*845Y*`MalEj3GO%d1u_tIJ~R zmYMpthURAcs#&(&G_T%ZQQM~S=GO9Mrnzm?vf5?ko7+TnLM~};J!>V4#{}BO|8noR zEqGE_wL+d)geP@%OIMUv$+7&aB2AE)kDZf9{H|MW)>oPJ_3h0Yt2AnE+Z1Rrd}*cG z&{p2mBuQx8baq?0{iKXoS|yW)b6VHO&G12H)iP5y#LcRmS&hurwl+1L5jU)HhP5(m z-`tvIR_n}`%4}WJ>ejfsrOs@b4Y0L-b=+*3Gh422tLxXdx3caWOUH6&To?aVXX;3D zLyLZuuVUI-8o(PiuWxT=xpD30`t_!6nG&O4SrQ{l%c^b8DT+;JV@KiZ*V=(;3q(uR zQqpuzYxDZ`%?*@kbNxos(yos!?c|>f+aju^s;Z_&ew1S*5>J=cMv)fX(6Y2@1yy%L zo5LZIj%(W7P=0y~mYfy+wyk4vv%T~5=C&AFz5&jD;wLcWcC}WY%keb|wWjLyh?a*9 zZ9YsH5S_~EYA1%(Pzqfl>1lZl8#9R+kOb>CN`mDS_hn}7Y8HswL(1b0rKQRpiFxe`vw2;EQAV86ii4_}WmV?%P3sVs+8T+0e#v3Ws3aR$H*Tq4+vH4_ zTRY2_FO?l3l{Q#1)!cx@SYhB- zwL>-wDl6-j)>)n;$f>GUBbSLgv&1gmk~zRSsQ#A$O+?b57s0J^g4;NFY{Rf;lu-jCiCvxkdwG-Is0ao%5mg&eOQD+U z{crhNR`#sFd@66_b^;fuxQ!!YJGqVm(}{F>SGs3Fkye1!$Zw!*m8o8CC8c_~6{_mx zRuro1tYlT!S>dU!v!YYY1P(#0USVz4Qg`5LtF80eYDKb%c^uuw8MauOc99xAE{Lu2 zc4ga+k%7AMG4XZFUBlplSyAakvvP&?MbT9A>7uD7vS$?OU+UaXn1S?27*0U?fdf<+ zLjL{=^3tY*kgOFFBykyT4Ms~NJ>lFlkki;sY%oYf53 zX-Q|5CdFH|l(U*4J1yy~(r$UHmU31zWTz#aRa!4^)l$xChU~PYv$`U(YAI(mLv~uy zS*^$LLOI1ZiSyWVB1s@R2Vch~iY|XrF)>)9FoIZ+;I#cy--(7?Yo z!C&V2J;l!cn-lz13I5sye`A8b+4Hx0|Mn#Ke@*aTOYrw3`0pq9pL+gLDLL`?n*{$v zg8xf`e?Gw%z1`){ue^T;Ciue={4oiBL4rTg^CA5|y3+X{`1=z4FBAO3o)7VPBEdhF z;QyT9|K|DYH@f_(@$m`#>I8pgf}U1V5DEcP98ZCHS`{_-j1>n3tF968wif z|Jf0jo=+tB+Y}A71pg<`|JujzxddPE4p+Xxe1_+LG#`CR3PClMW@J}cB=MsFuRj&Nb_wJ8K@JA>3 zS0?y{p6~YVS0wn86MRF0Ki%{1chNU(3I3b}-=E-z6a2-VAM*KiX@bAP^B4N~UY+2t z_536Ex(MBr;BQIrwX5u|92An_Y?e&6a1qI{!a=1FA4r134Yo;U3rG^4^Hqe zOYjRlUw*esZ%u+1-`_;)1ucYFSJ@BYRF|M3KW zOM<`M^I!1pznm7LaOS^E@V`m$e@O68dHz?0&V9kvuKZ^B z_{~W02POE!J>Tx_AD7@4Cit=hU*q}B-u+3QKil)G6a1P4zdpgYdHx*lertliAi-}- z@I#(I*So*i^IJWCNrJyL!M`!Vzs>U<-u-(L{QDF94GI2Z3I5Zb@AUrNp5VWf;O|QC z-}d|k-u(j!{wE3k-#y>u?f)*p|1rTomEiy8`9AO7yvxZ~;ENOd0SSIifIS>*XI`1%m!{b|oH@#c4Xd0*=J2Rwg@=RN|zti(ad48AYQ=Z@L`Nf`pmFKHH|7y>#^86*9Z}j|YJio#7 zVSIX)=fn84$Ma$QIqLZ^zI=`6!}#$no)6>0_j*2z|8DYp7~kFM`7nO_s^`y~<|@p0 zJwIoWYMwAL;qyJ%5zvD?C5X^QUG{h%-{tvtdA{58H+nwh=WBaBKi~6T_55+3zsK{( zd;X`MU*P#)dA`K+Pk8>#p8u=o-{Sdyc>b-PpZOk_e^+|`NY97)<3i7uY;xsQ;rVjU zukd`8=Nmk~)bpo%ewpXb^n9J?w|M>}&v$zMWY2H&{A$k+dA`~6&w>|%Q z&)?(uH+#P8OqZUkJipEJH+%l|p1;-eS9tyo&tK*FuX+9xp8uBT@AUlrp1<4kVZ3~g z=YQhOf9&~3JwLzI#qW=vzi_kT|LplNKYljB7rob&XD~k~!O!#jo)PEY@dfN^_`12BcUxFX? z{FlA^S10%@68zN({zIPsig*9X1b=&i|9XPI-}B8EIr)3o^WS=p1Uap8w=89DlLrKjrz?dHxp9U+MWzd;Yzi|BUB9?D@}n{?ne{G{uj{(GMPyyqYGyo=q7|6MXFExC;S`f#sXFI#A6v7w8DCS7UcrVaJY8yB0%SeF@2 zplYWV!%N$m*J8HG-vaIK{tGTQoz>L3X8oqK7n>G$e>XQ4@=gO6;H1$i;|KOyX?)>I*z1~#m=8cUZ z^v&*?Qx`4M$%&zU(pI{dd<;K2~VGbZ`u5Nl^-`9W- z0Jj6525thBYa7Uio#i{1)uuSrZSVc@H{o;ZVtp^y9Fx|PQG3CnuIU;=54~LX(Q{X! zNu|yj7#pAH8a#8NZ)~@}XWMEnmhi|jDqi*qC0>{CzGNJx)VEjGbM|i+ z)&)iH`hD<)WLryxlDovbj|!2V#GkE*%MI zS|rf$U$_{U2TTJF0;U7AfEmDSV5Z0VCSLh0vjc%(CV%Bt_z*z;&H?moE^w#^eV-5L z`%*xBz2_S|-vX8!bf1 zm)eJy+QXOniSp7T^YG;Wq&{K?{EzxEWfw zhK2Jz(86BA>^Q z!n=Xug6S4N3OyTIcZy2ZbMJ{(#&eLm@h7XC0$1}*#ppblDi#s$VS zLJP0xGG;5Z@S1L94_f$bz$MVa&j4?N7Cy9>_(2Q52e<`V_}~F!Zi5!yIBd*)(83!h zjCo8nxcExS4qEuYtB7;abc?V5An}A2{uR&&{S^3(AL34WXyGpcTcCxX0k%R57hO+T zKnt$``k{q40)x=P+kjDM;a$KuwD8s9p@qk8pzc5mUnzhVzU#x(3uxiukC0wy;cnnI zXyKm&cR~wK+(bD-3t#wAV;+DOe)wkc0b2MO;CX1_qd$Sa#nUZrhn@{B{PeBl547-E zpCf;ug_quetU?RF<4edVwD8$qCLf@M9|Ojrh3oF5?4X4|4qOE-yy9 zhbU8Miyy{4bPM<^z#z2n2Y*I+K?|pTPCEuI{1@OVX!8j12d;w_{w8n(wD2|mPW^%w zegU`*TKM!|&=x@pYk&S8XyIdiNm~Fd{1M}P1GAxpuK?yi3-19AhZa6~ALR-y zJRev9ExZXRg%*AdPzEhLo1VB5TKIC{G-%-u0WHwNUk0{73tv`HU@m|b{unR{Eqphy z3tIT-!UA(CwDA4F<FJ3%5_l4YcqpXB3zR zp@nY*9)lMCH{dB~;a40~V4j5*{_Md8X3k8?9lT&pfk{CNKm0P%3oZN%unt=ItBXi4 zwD6450@DvIyZ{)77GAQrz+3_?ybpL2wD4=n3d}Xo!XF22f!+iD18^I(aPJb*1uc9R z@Bp;%+KK}62(<9`s!0p9@Zy>RGjkUGCU^*#11n8li=MJ3#(J3%_re{D&6)SsGb_ z7XBOXJhbpHcNUo90~zyx2X_^idCMJypoO0W8li1Ka>DdE`=7ZzXh3r7C!FN1?F04;YQ#V zXyIQ0w?PYk^0UMhT6oSL@(o(}LEuSf;fh-e=wzl_{PNEsL(szC1dfFkZvQ-a2rc~1 z+sGei;m$9RUTERh-cCOPE&Sd)kbh|5@-Gr+XyL=ZL>~byd=;<3S0v%{952TXyG^inDz%+_>Vs=Fn2-=f8=4>RcPU(e@Q+= z3;)-zi8HkMO@VnUFlWwmi;sT{nT8f_djgq+7T)_GlqIzJQ-Rs`6!C{11vmZ$nSmBQ zd@u3CITNNJ(D1bP(w6mSi+@YBEz(82|a3(ZZ? z!YSZpXyN05TcCx@fIZN{O~7r?!mYp^(87bjozTK>1MY$rege1;TKKQPBhbP#P9U$K zh35m0LkoWpcnVth9>5$*TLJzrU?#Niu_qRqInct_1M{G75MF{AwD7+GrO?8k1S+A0 z3n~gt9klQ)pb=X5?Z7%{;oen+rX5;%_DO}NA6j@NFb*yJ3E&cF;i{7h&E?R-kDpR# zu7Vc6yq(8BA_C^TE4h2IDCLkoXsJ?VuO ze(%OYb1Ag&&1V*xE1`u?X)iR_K?{HBTx14X_#41o(89-@M}2`7UI;u0T?u|4@DQ}{ z?}5jmg-_p#3_uHC4Vc53JA)qtW>bRYxJ!r#7te1I0dy_a%_7Cy3%e1I0-3N%6s zUk|iE3x5{a0xfK|V-H&RS)gBZKXn!uhZer)Lh2W^@bp2-THnDZUPM_#3;)|VWeqL- z)d|WMTKMG`Q>M@<@CfiEv~crI$`@LA-7eC2#B__l2z@NH@XL3TE@?E|#%pDrUU(85>0k+eYH z0ABbe(gH2~o;Oo=(87z}M%h6Nr>`nB&qE6rzKb$ClDRqf{A-X;XyKOk5PxXlU%!`f zffhdbeZ(JHxbgkeL)iiE0Y;&PXMBLNfENBHunSsv;Cf^lTDa{a$RxDzlfZ4zPl0c| z37LczzVxG%CA9DtfCr(4w|$IsK?~mlJO(X%-^VE)`k{rFe-?Rz7Jd-ug%SpY4;@D{^|piJ+$z` zACMMk;T1om{X80fz&*gR(4*idfd$aQZ~qZ_3N8H62Mf(f(0jn|{W0l+7OwaSvI<=Z z9tC=#g?|eSLJL0)Tmmipik~8%(8687RnWqh0@pzczXP}lTKGf2EzrWB25y5EzEc1# z{9WK~XyG3L_dyHqe5lYo04@A4z(df&*8q<|3x64S6k7Q24^!^Y!s~uUyrG5n0On<+ z9bEl$#XyM-hTcL&D`0uo_(86B(@N>W%XkqhL@)=sV0GJ0YJOeluTKFI!1uc9SumD>4XrL5Y_?18z zwD1a`5?Z(osDl5jq7v7g!H{0r>Vg zMP@6s@av8&GQH4OfqxBL0{s+t&rwC@P0+%B0j_}-t~;j4+yE{7)XR&^ZL$v@enpYF z8(R2U;67;K;#ZO`XyM{ik$FTk_>}o_&$tnMAyE7Z){($Ff!WZPf`1Imffn9y9BGFZ zz7R;sKG+;zWJ;lhF9j;0g>M8-f)@S+a2mAm<3J;{@caej3AFIjz!qrXIVDA=A6j@T zFbFODXJ8as_}qo$6}0f3z?-0j8y6LstDuEHT&28V+y%ZDxCL5x`7-hsTKEUR9niwh z0QW!(|JQ~h^8mE)2R9-Q(89-UqKu)1KMEATk}?7x*FwHQ3;z_D2Q6HFCUOET{4Jmi zTKF$O9kg&67fGB3E!+ugffhdGydrY}^c?UlTplqBy$5_da4EF#oxtVLcYzBpARnNG z&jW6Nz5x8nF47Av{0rbtXyJbV_dp9*_mEy_;SU0jK?{EgcoJIp&%KlrwCO7{Yk`?5 z)+@lT0}h84z7IGSTKJA_$Og3Vq1%xMXyJ>1b9AJ$J@fEFGE#-WA33|tC* z7kJtL^%Gk7bl_U(7V!Iko1lg71@=G-Z{yO7JD`RC0Nf4z6!=gszqk)N1+D@ff<6uW zTHsOWOTkwGk3kE64|p8<0dUz6bqBf<{CePN*#XxNBOB1d>wx0<^yA5>q8n^^nc+=~UA86q(0#`u`AM<+T2U@rh*aIy*0^9*Dd<}3HwD9MFd!X+EA9Fc* z3oZN<@EEl4$~O`=wD4ZQ9LKlWv#v1U3OQpAT;>sB$NP2T;t><)FF;=5)Q{DuEWDgAyrfoATd<9;1~!+m}kJH zf)ZmIDE+>s?#yp~ubF%A?pk;Db*MdXVj6NjIB_SWo&AB& zLps2TpZpT)5PS)6IV1^AY=LCJiEWTPIB_*(0K5ZuFXU?QVciN}5e`!@Jo;PsF;$fc%mVKK zjzEqBC%z0h9ef=4>Ayovz*~W83;Y2mu7Gra6T2Z9@C@*uAOqmUx^JOQz=_Kto56{B z$QE$o2xJ>L@h!*>aCJ*q-STbt2u|Dyc@Ug9N&zRn0NDpl{4dBDxY`<4Qz0*d6Tb{O z08TvqJBTkh@e0V4d04Z+pF@rYC;n^*aR4WN_EyvnIPquSM;zD=d>hgVPQ39p#1VWW zux&eH0^R|91u_6mjQl;~08V@nvKgGX@D9WQoOm&08#r+bWIH(VEy$hV>Ic|kA;aLr zBX%Gr;KXl2_JI?hhKzv|EB*m-04H7s83!j0Lk@uxulXVB;bc5(0B?fK0^bU}3vwJd z@fVPJ;G@8QzYDbiJ`U`-8|xgLcn_ond>Ht1NIUxjFTDrx0Vlq+3+n=$xcWzk2{>`l zeTXACaTR16IPtQF!|FEh4&W$c7@YVlK~l=G~@s{ zF*FL_!RvrmL%dT^1Hj83h40|RZIHR(#9u<@gO38w*@JxpdA&ShyytB zF-SW&G4vSLBzPV0QAii~81QqCBM#sjfXDBJ9pJ>j{S{&YP8{EdIshl$@C4QZ_(tGO zkO#qu_d#}p6aN`93Qqj(lduDvIPWRc132-QkOSbv`+kkJ08V`5X~f}Fe8&g;)iYQZ z;KW_eVmvr;Y(MrxaN-HiVLt>X#$G_I!HKyS;UhTl2}lN<7<&n8fqj6FLN&0SFjeqi8sE6>oPd;l-H4KaAE@TCOGjs5d82$?EvnE zOaUjJ{04FjPMi;!3tkUA7cvig32+%?J~;7w$m!s%z&9ZE;KZu`L=Aw~0bAe1{TTZI z8-9zJfD>;$h#COj0el_O15SJwk^v_^{ucH`@G;w(WfM!|{u{u_A(9|JbOhnfQ?-UE3DoOp)v z)RYDI%>eKY$Sm+3z{eoRffMUO9=?A-9Dti3^T3G@K^B1%--av(C$6mU)G~15Rge~N z;(d^IaN;>(Pj!HA0ERse-%;TE8sK8c064J^vVnboFIIZ$I&k96)t=e{PCNkF4o*C; z##48K6SI(e!HK_v+z+lMdFn*SD0n^adyswL#5uK|dLEqkACo;b4o%99TUaezHICXvh-qdf?hR#0Q+X7t#t&d|-yB zE(a%`bcCle;2ppo$kpJ)9ApFd;s1U=B7kq3Rmj8d4mt%Jfir8>%+O3VrG9cpZDvwy z&2ZJIH&!t|iT!ZC9y#Q=G5r4bRS5SHqI>>5wAJGGxjawQb8)`^jmX@g$_iE45USTU z(^hj_Q>khiz4~xRMJCjW_HzBJurD@N)Q38(K6c+W=%!AhX)lMhRjPJpRefcLmkGC4 z;ODT=-6^`wJQ>VmIOnAAb(2)xkQ-g=TxY;Jj@cj3Zv3AyNzDjdpla(Ub<||4TdRh> z(TXu=ZrhGyYSl5J_dHjlwvYaHVk~3YX~onYZ*$r<=xV}v#;3uGkDDV~$CPR{rL(5K zx}z#n+3F2fj0TK1c5%LPF$%JyTZlZz_fCNy)d z>uE^WdUZufyK7f%rK-i;em0=DTr7uNP3Y_AB%^D7Q^B*M+F<^JblQ2eIs2R3*lUID z?pkuk+5M;0sA&zwJHte6x_Zjho}c^rvU^w196@WE^9`9wC=(C2&j;-zWIQ5 zJ$8Dj@4Ef8GVL=;_e0%3U|-YivrF|D+um<&+V@0uEVa&Z*RoHQ+Qxbeikq>)pvgD; zv(3I=V%+RKP}@*?KdJYU!GIjPwM}~l1FmWN1I7mJLwf!{F7?&;5H!ZFgPGO1ANDE= zyE=Tf&)e~~+l~GYi?eIgYy{KxbYlPiusUp8m>b)ka(STtZN)o5C@3a=?lTkjh>g(C zT(=b><6pB+C?9L%hu(jxO81{~_3G|_Mvp# z_)#P%cGdVUIy9qjJvaL7{FvPF-Y2N$&DsmnPyZVO{I~V%`>utmkS)w(=Io&F0nan$ zSxK+S(+bzIAYDQGMv$#K$FobKUvf_qq%Uw!KD$(xwr^Q^UxRcrhMfU1{HX2jJ(V3} zJ0H5Hy~6#b$*-wtgXPw(JJ0kQH9gd_I` zWD8Xl(ss-&wxe8qYMm#PUh`-N*MV6ltRr`iGS?jwH|ErRhV;*6ChoiRzH?*g8n^Re z^l95pDz(k^*W^j--dMV)G5;g-xq#74zqI~<`vdkLlK#g3a%k5U>%o3r=NOYuGp}1S zrrkx34T_2BXV1&N_OZT>t*R`l!x}!<&GKu@#EZIk-+<3ACxvD!dK}VgVy>Ea2GcQ} zQ<^WMgY%ltTx@%3la6n|^Juwtj&GA^ahtn)j_s?lL;LUv--n{MnBQiP(XlwzkA)t0 zs^3TNv1b?dSRISg3$f7a>Zam#ML*q~2DO>IxqVGL@eTRxD+_dewqyH#=X`zsJ%T;P z_J1NbHw5e@dlnK$4GG;b=zMQx7jso*C>4% zl=WHe+F;fl&kqG$Q%u}Vt=RF=b4lIkTK&_~IBEYoiv4$M*1j*({o9KBYoAvZ`>gwI zE$*l5a#f)&_3^sGvDUMySdaE0QCj=VJ;kmK?PH|WN8NsDX}i|F%GYhK6L$QlKO8R9 zkXx^AUD&p0|A&hGx6fU`*8E%9=qe|x7)`( z*7=%WoG<##&jsDSCCA)rPATM0+q~rMix@7X;`e)kRagDOKnB1Cv zAB2n-`_OlCBb?X1FPIiq)9^g2@AF)nT%YV3G4tE@^1Qiby7Sm$gX+cD>W<^_p!oy} ze%SNTKKowN9UJt#5oEWSn|AHD?^H0K8v{F+CMULUc0A2ojJ@gWqV11;j@MmVuhFGH zgR3ljCTH?$k8O}^H&O2u3rhD2dtRgCqoT3tgHT^P=PJDq(x*LgUuD}s9V1qp>~leJ zFmviXcaF1ek1woS?f=Zu`*wYuI;!|OrT4dEOYbjei@C<@`Hn8kXY6p1wb*FYtvRpl z33zTQ=dbO%w)x~@o82{F>cGrru8DeYbe@5O)}a~CS};0Hn|<7d+OlmnwPX6)=iFGB zzIGp*8yyB|=ZLc|sl%Li^_%u1Cs!UB`lOm3mh0Vc^=Rc-cwB!cY{%8+W-RS7dv=g* zcAJTd&h1I1Yt87?*OW60waMJ=v7VJpTzHKh3AjeHzkMHP+ii1WPr3b%*M#@$WBX=o zxAik0Lry+;J}c{*`LOrw4|~q;=f*!M7sdyD4|tk?52!y|JFWPh()2U&<*|KTw0$#u zh(Yako6h&^rTMn!(|wK#?lUNPFi4y17`SnC^Kblck+zq+&vSL!{?t{fx{&8hJZ~F6 z44PW7V`R9kryOTr>fp7_j5A2z8mzT|-$|L>Q-5wT@f~o$cL%orjhKUVzsdQWSAXw2 zx%4*XthrB{V}p#j8*f*K zokRAwkKMkkwG#vOE4>z1_;zf99p&~ld#zLVh{?S8urt61Q;QsHuFb~Z|BDXSHxn}# z>C65ApN$Rde?1Jip<2wV!n|buUaZKsHL4CTG|E#o@?YWtM_GMcG;^+Eh=hLob zw=V4582tv#=Nj&K`x<5X8Z`d7_OQJZ_VOOA2fu$EbgnP6C#%_L4}5lYb%-PTFoCUcqj?f99P8gz4N_YJBe+wURC(NQ((sOzfLi~;}=Z>@Iuw$+7 zS&sMbSvaN*HXGFU`KK1&=j(e%>pM5o*UV?op3l^XJulCjd#Rv($?j`&6VIL2HQLN+ z$B1LhI^bL*QtO;6>s*uVz7k)+;s5@hBCvRL^2E%uo|~)|KR#J)d9=_r{ydKN7LF%6 zgDig1Z@)nBGQl3f4T75lZx`GpI3oCn;C{hFg43Sz_0JJpB)Cj)wcuL8s|CL<_&vd4 z!BN2jg7|}M+MOc=7Yd#$*ebYIa6s@YfL4)ZGx8zt`*D+eqQi8!OenO1aA}E zDfnZ-`vvz3jtRaZcu2738L?AvuHZt!vjoo*JYTR~utP8-I3V~1!Ht493l0h1E_kQl zu;4EQ_X>^)9uPbzh(F?|Q&lTCOK^_he8GCba|D+NULe>ec)4J=U`FsN!9l_61vd+B z5!@!YUGPr9p9nrExJU3w!RG~E6FekX@vQIP6u~0}=L()Kc&^|w!HWb}3w8=71^Wd* zFL zt|^VTdMkCif7~0tES-vTa2D?mXZY0JJab7ro5NA_z*6;g#flUn98D&!Dv5Pnctvhm zW7EnOc-x5Adp8f@&s3r967=qBN_WLu@pf=^VHoH8O9rcp!Y%1&m&6|)UKRcfUV)yV zqW1KqiLQ84Pc*CkF|1!E-WtzpBk^i=?Ls_@w+6J~z36#e9(WG-+VV-nW_e#q7eafq z6E#%9)~0AC- z#aO-u9e*I!v}ot?mIUw8Xd=Hnon497vnS&hcdli(X_Y2S?dgfwsk8C-wwofkOgzSy z(MNpCQB|Kpdn6f)@V)98Bqq|0P^pX1+LcVA3u2(VBR^HwwqH zYjSwkcqEcQHN;c>c;7g9y3<#pK8vGdES>7t=kb^QaopP(iS=diVtQ1?U*R~DNGG_a zqTt9|B$~-Y@>ms0#r&2&%qy?4M|G=6G}hadOsVx`dgXl%tT(qNqD{qq5Q+J0BUn#Y z0zQ5Vj9ZApzuAtU4tnEfEmG>IDiV+N=hXwiRDUANI)4b<_ws2K>E_v&fL-ZKJf(gE zhLw|yr`F_q64daD--4jLI>8Q*rMhIk*ZiaIL^hY_h=cwy!pg(n;V7Bw?CVzVg7qQ; z3ct)p@RG?)G~c662u0S=hCczu^Re|^>Rhm1ao~cG4l4?-SKSqgB-7aMjBj`A<8^p< zN+g+!$Jf!^zpRM#u9FU(6%om87Z|xu}sCv|fL)3CNTqjqTKTeSx~AEnJtk!2Gx zbWqP|-qn{;uWFXk&9lN0ndP)_1aEB5ty8KVER{^8)~N=~(y?`FUpTTJ+cbQBVuDq9 z(av->k2M#~rxSQrJTb}le84$)sMHCbpWPF^Zi(?cuNyhz4tcWI-JR^q^(b|!*Nuo} z(m6F>vz$7^>(+kOd)=9AB9-q}4Pbrwm^xEqPMzg->j<1pgjMGd^_cU#Zp4Jz8oh3@ zx!Ge%@YV?;OJ7~&C34YZs;^gFtXZZfs@lPl>GjwXR)Oh>)umv$Of(i(tHJs*8Jz6~ z8|X@`!D3TuJ)O!p7miXH9HrJUA6Iz25`zJ+H{Po=aHVD$>{3^e^``sd>S{9BtJI*U z9aMkgU6Er{Z}i}x4&ztB{GDu*hpsF^^)<47(yx2D#F|tTalggOTvmGE87Xvb}2l8-ycQ#AAkK0}9{WZ4*o|Bx(~!K%Acvph8JBGVcFk) z>H!Z8I))E|`SE%PY`x#89`^c8O7Suo+!OtQ$c4>q7d5w>*>DCnT0ZN|g58JZ;}L`2 zTz@Q^H<>k0@`o{0wtji$i!yI=R)cL)Y{I^6gN6oD3P>k_=xkN<7id7*zi O?xQ|9{J)P)1pWs#T { + private readonly IMemoryCache _memoryCache; + #if DEBUG private readonly HashSet _usedids; - private readonly IMemoryCache _memoryCache; #endif public CreateWpPages(WpState state, IMemoryCache memoryCache) : base(state) { + _memoryCache = memoryCache; #if DEBUG _usedids = []; - _memoryCache = memoryCache; #endif } diff --git a/publish.ps1 b/publish.ps1 index 5e2f66c4..4751d593 100644 --- a/publish.ps1 +++ b/publish.ps1 @@ -26,8 +26,8 @@ function Invoke-Publish { } # copy installer scripts - Copy-Item "Publish\install.cmd" "bin\publish\windows\install.cmd" - Copy-Item "Publish\run_shell.cmd" "bin\publish\windows\run_shell.cmd" + Copy-Item "PublishFiles\install.cmd" "bin\publish\windows\install.cmd" + Copy-Item "PublishFiles\start_bookgen_shell.cmd" "bin\publish\windows\start_bookgen_shell.cmd" # copy assets Copy-Item "bin\Release\assets.zip" "bin\publish\windows\bin\assets.zip" @@ -37,14 +37,22 @@ function Invoke-Publish { .\bin\publish\windows\bin\BookGen.exe version > .\bin\publish\windows\version.txt .\bin\publish\windows\bin\BookGen version > .\bin\publish\linux\version.txt + # make docs folder + New-Item -Path "bin\publish\windows\docs" -ItemType Directory -Force + New-Item -Path "bin\publish\linux\docs" -ItemType Directory -Force + + # copy license + Copy-Item ".\LICENCE" "bin\publish\windows\docs\LICENCE.txt" + Copy-Item ".\LICENCE" "bin\publish\linux\docs\LICENCE.txt" + # Generate docs .\bin\publish\windows\bin\BookGen Schemas - .\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\windows\Schemas.html" -t "Configuration schemas" - .\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\linux\Schemas.html" -t "Configuration schemas" - .\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\windows\Changelog.html" -t "Change Log" - .\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\linux\Changelog.html" -t "Change Log" - .\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\windows\Commands.html" -t "BookGen Commands" - .\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\linux\Commands.html" -t "BookGen Commands" + .\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\windows\docs\Schemas.html" -t "Configuration schemas" + .\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\linux\docs\Schemas.html" -t "Configuration schemas" + .\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\windows\docs\Changelog.html" -t "Change Log" + .\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\linux\docs\Changelog.html" -t "Change Log" + .\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\windows\docs\Commands.html" -t "BookGen Commands" + .\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\linux\docs\Commands.html" -t "BookGen Commands" Remove-Item Schemas.md # zip @@ -66,4 +74,4 @@ Invoke-Publish -SelfContained $false -WindowsArchiveName "BookGen-windows.zip" - # Self-contained build and archives Invoke-Publish -SelfContained $true -WindowsArchiveName "BookGen-windows-selefcontained.zip" -LinuxArchiveName "BookGen-linux-selefcontained.tar.gz" -.\Publish\mkisofs.exe -V BookGen -o .\bin\publish\bookgen-windows.iso -udf .\bin\publish\windows \ No newline at end of file +.\PublishFiles\mkisofs.exe -V BookGen -o .\bin\publish\bookgen-windows.iso -udf .\bin\publish\windows \ No newline at end of file From ec8f5a7d8b2cf428d10c620d628a6f34f0759db7 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Wed, 4 Mar 2026 18:24:20 +0100 Subject: [PATCH 40/41] Changelog update --- Changelog.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index ab6225a4..850712c2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,15 @@ # 2026. 03. 04 (Prerelease) -* +* Change: Updated dependencies +* Change: Better help rendering in terminal +* New: Support for config overlays, which can be used to have multiple configurations in the same project +* New: Added Spellcheck command +* New: Md2Terminal command, to render markdown in terminal +* New: support for rendering [nomnoml](https://www.nomnoml.com/) diagrams +* New: ISO image build for Windows reintroduced +* Fix: Base64 encoding of images was not working correctly +* Fix: Md2HTML was ignoring -ns argument +* Fix: ImgConvert command was not working due to resolution parsing issue # 2025. 11. 16 (Prerelease) From f645cd4fa473ad50b60d14dd5d95fb9c2f143c35 Mon Sep 17 00:00:00 2001 From: webmaster442 Date: Wed, 4 Mar 2026 18:25:48 +0100 Subject: [PATCH 41/41] Dependency update --- Directory.Packages.props | 4 ++-- Source/BookGen/packages.lock.json | 8 ++++---- Source/Bookgen.Lib/packages.lock.json | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1a203868..909874e4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + @@ -17,7 +17,7 @@ - + diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index 941e6f74..6cfcf66a 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -4,9 +4,9 @@ "net10.0": { "Markdig": { "type": "Direct", - "requested": "[0.45.0, )", - "resolved": "0.45.0", - "contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg==" + "requested": "[1.1.0, )", + "resolved": "1.1.0", + "contentHash": "ivaowI69dGxiyaKLy6+qo9Xm4DHwXDKB3orGIFOvA8k+eUBAV5UaW8GPIMc3h5jl7gZalyecR3x2t+nuLYdmFg==" }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Direct", @@ -203,7 +203,7 @@ "dependencies": { "BookGen.Shell.Shared": "[1.0.0, )", "BookGen.Vfs": "[1.0.0, )", - "Markdig": "[0.45.0, )", + "Markdig": "[1.1.0, )", "Microsoft.ClearScript": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.linux-x64": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.win-x64": "[7.5.0, )", diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index 9f69682a..ae9bb8bd 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -4,9 +4,9 @@ "net10.0": { "Markdig": { "type": "Direct", - "requested": "[0.45.0, )", - "resolved": "0.45.0", - "contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg==" + "requested": "[1.1.0, )", + "resolved": "1.1.0", + "contentHash": "ivaowI69dGxiyaKLy6+qo9Xm4DHwXDKB3orGIFOvA8k+eUBAV5UaW8GPIMc3h5jl7gZalyecR3x2t+nuLYdmFg==" }, "Microsoft.ClearScript": { "type": "Direct",