diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs index b4ac972f334c5c0a939b9adb06ebf1b85829b326..2895bd8bf9e76205a850132cfcf530e7b6859be0 100644 --- a/source/Models/FrontMatter.cs +++ b/source/Models/FrontMatter.cs @@ -1,3 +1,6 @@ +using Serilog; +using SuCoS.Helpers; +using SuCoS.Parser; using YamlDotNet.Serialization; namespace SuCoS.Models; @@ -104,4 +107,58 @@ public class FrontMatter : IFrontMatter SourceRelativePath = sourcePath; SourceFullPath = sourcePath; } + + /// + /// Create a front matter from a given metadata + content + /// + /// + /// + /// + /// + /// + /// + public static FrontMatter Parse( + string metadata, + in string rawContent, + in string fileFullPath, + in string fileRelativePath, + IMetadataParser parser + ) + { + ArgumentNullException.ThrowIfNull(fileFullPath); + ArgumentNullException.ThrowIfNull(fileRelativePath); + ArgumentNullException.ThrowIfNull(parser); + + var frontMatter = parser.Parse(metadata); + var section = SiteHelper.GetSection(fileRelativePath); + frontMatter.RawContent = rawContent; + frontMatter.Section = section; + frontMatter.SourceRelativePath = fileRelativePath; + frontMatter.SourceFullPath = fileFullPath; + frontMatter.Type ??= section; + return frontMatter; + } + + /// + /// Create a front matter from a given content + /// + /// + /// + /// + /// + /// + public static FrontMatter Parse( + in string fileFullPath, + in string fileRelativePath, + IMetadataParser parser, + string content + ) + { + ArgumentNullException.ThrowIfNull(fileFullPath); + ArgumentNullException.ThrowIfNull(fileRelativePath); + ArgumentNullException.ThrowIfNull(parser); + + var (metadata, rawContent) = parser.SplitFrontMatter(content); + return Parse(metadata, rawContent, fileFullPath, fileRelativePath, parser); + } } diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 67f58c3b86d462efdad3dc788d531190757ab01d..a70d290c117b88139ae321df3c5904adb471bb50 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -1,3 +1,4 @@ +using CommandLine; using Serilog; using SuCoS.Helpers; using SuCoS.Models.CommandLineOptions; @@ -330,13 +331,20 @@ public class Site : ISite } } - private Page? ParseSourceFile(in string filePath, in IPage? parent, BundleType bundleType = BundleType.none) + private Page? ParseSourceFile(in string fileFullPath, in IPage? parent, BundleType bundleType = BundleType.none) { Page? page = null; try { - var frontMatter = Parser.ParseFrontmatterAndMarkdownFromFile(filePath, SourceContentPath) - ?? throw new FormatException($"Error parsing front matter for {filePath}"); + var fileContent = File.ReadAllText(fileFullPath); + var fileRelativePath = Path.GetRelativePath( + SourceContentPath ?? string.Empty, + fileFullPath + ); + // var frontMatter = Parser.ParseFrontmatterAndMarkdown(fileFullPath, fileRelativePath, fileContent) + // ?? throw new FormatException($"Error parsing front matter for {fileFullPath}"); + var frontMatter = FrontMatter.Parse(fileFullPath, fileRelativePath, Parser, fileContent) + ?? throw new FormatException($"Error parsing front matter for {fileFullPath}"); if (IsValidPage(frontMatter, Options)) { @@ -349,7 +357,7 @@ public class Site : ISite } catch (Exception ex) { - Logger.Error(ex, "Error parsing file {file}", filePath); + Logger.Error(ex, "Error parsing file {file}", fileFullPath); } // Use interlocked to safely increment the counter in a multi-threaded environment diff --git a/source/Parsers/IMetadataParser.cs b/source/Parsers/IMetadataParser.cs index 84d293089e8c46db6ecc3f46f8a5f0838d69b678..21974a94e426209de83929f6a9f238f3e75e826b 100644 --- a/source/Parsers/IMetadataParser.cs +++ b/source/Parsers/IMetadataParser.cs @@ -7,22 +7,12 @@ namespace SuCoS.Parser; /// public interface IMetadataParser { - /// - /// Extract the front matter from the content file. - /// - /// - /// - /// - IFrontMatter? ParseFrontmatterAndMarkdownFromFile(in string fileFullPath, in string sourceContentPath); - /// /// Extract the front matter from the content. /// - /// /// - /// /// - IFrontMatter? ParseFrontmatterAndMarkdown(in string fileFullPath, in string fileRelativePath, in string fileContent); + (string, string) SplitFrontMatter(in string fileContent); /// /// Parse a string content to the T class. diff --git a/source/Parsers/YAMLParser.cs b/source/Parsers/YAMLParser.cs index 7c1fbd79f00a4ea0dfe142c54506b394578683a0..6b186f766728fcb57494232075e2a448682ac646 100644 --- a/source/Parsers/YAMLParser.cs +++ b/source/Parsers/YAMLParser.cs @@ -1,4 +1,5 @@ using System.Text; +using FolkerKinzel.Strings; using SuCoS.Helpers; using SuCoS.Models; using YamlDotNet.Serialization; @@ -26,45 +27,71 @@ public class YAMLParser : IMetadataParser .Build(); } + // /// + // public IFrontMatter ParseFrontmatterAndMarkdown( + // in string fileFullPath, + // in string fileRelativePath, + // in string fileContent + // ) + // { + // var (yaml, rawContent) = SplitFrontMatter(fileContent); + + // // Now, you can parse the YAML front matter + // var page = ParseYAML(fileFullPath, fileRelativePath, yaml, rawContent); + + // return page; + // } + + // private FrontMatter ParseYAML( + // in string fileFullPath, + // in string fileRelativePath, + // string yaml, + // in string rawContent + // ) + // { + // var frontMatter = + // deserializer.Deserialize( + // new StringReader(yaml) + // ) ?? throw new FormatException("Error parsing front matter"); + // var section = SiteHelper.GetSection(fileRelativePath); + // frontMatter.RawContent = rawContent; + // frontMatter.Section = section; + // frontMatter.SourceRelativePath = fileRelativePath; + // frontMatter.SourceFullPath = fileFullPath; + // frontMatter.Type ??= section; + // return frontMatter; + // } + /// - public IFrontMatter ParseFrontmatterAndMarkdownFromFile( - in string fileFullPath, - in string? sourceContentPath = null - ) + public T Parse(string content) { - ArgumentNullException.ThrowIfNull(fileFullPath); - - string? fileContent; - string? fileRelativePath; try { - fileContent = File.ReadAllText(fileFullPath); - fileRelativePath = Path.GetRelativePath( - sourceContentPath ?? string.Empty, - fileFullPath - ); + return deserializer.Deserialize(content); } - catch (Exception ex) + catch { - throw new FileNotFoundException(fileFullPath, ex); + throw new FormatException("Error parsing front matter"); } - - return ParseFrontmatterAndMarkdown( - fileFullPath, - fileRelativePath, - fileContent - ); } /// - public IFrontMatter ParseFrontmatterAndMarkdown( - in string fileFullPath, - in string fileRelativePath, - in string fileContent - ) + public void Export(T data, string path) { - ArgumentNullException.ThrowIfNull(fileRelativePath); + var deserializer = new SerializerBuilder() + .IgnoreFields() + .ConfigureDefaultValuesHandling( + DefaultValuesHandling.OmitEmptyCollections + | DefaultValuesHandling.OmitDefaults + | DefaultValuesHandling.OmitNull) + .Build(); + var dataString = deserializer.Serialize(data); + File.WriteAllText(path, dataString); + } + /// + public (string, string) SplitFrontMatter(in string fileContent) + { using var content = new StringReader(fileContent); var frontMatterBuilder = new StringBuilder(); string? line; @@ -74,55 +101,12 @@ public class YAMLParser : IMetadataParser { _ = frontMatterBuilder.AppendLine(line); } + frontMatterBuilder.TrimEnd(); // Join the read lines to form the front matter var yaml = frontMatterBuilder.ToString(); var rawContent = content.ReadToEnd(); - // Now, you can parse the YAML front matter - var page = ParseYAML(fileFullPath, fileRelativePath, yaml, rawContent); - - return page; - } - - private FrontMatter ParseYAML( - in string fileFullPath, - in string fileRelativePath, - string yaml, - in string rawContent - ) - { - var frontMatter = - deserializer.Deserialize( - new StringReader(yaml) - ) ?? throw new FormatException("Error parsing front matter"); - var section = SiteHelper.GetSection(fileRelativePath); - frontMatter.RawContent = rawContent; - frontMatter.Section = section; - frontMatter.SourceRelativePath = fileRelativePath; - frontMatter.SourceFullPath = fileFullPath; - frontMatter.Type ??= section; - return frontMatter; - } - - /// - public T Parse(string content) - { - var data = deserializer.Deserialize(content); - return data; - } - - /// - public void Export(T data, string path) - { - var deserializer = new SerializerBuilder() - .IgnoreFields() - .ConfigureDefaultValuesHandling( - DefaultValuesHandling.OmitEmptyCollections - | DefaultValuesHandling.OmitDefaults - | DefaultValuesHandling.OmitNull) - .Build(); - var dataString = deserializer.Serialize(data); - File.WriteAllText(path, dataString); + return (yaml, rawContent); } } diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs index 4f8c8ffb9e4eee7dfa7ba298fc4698170d3f7df3..2b24f24227546d04a00e3fec24786b6aa95aa3f1 100644 --- a/test/Parser/YAMLParserTests.cs +++ b/test/Parser/YAMLParserTests.cs @@ -93,7 +93,7 @@ Date: 2023-04-01 public void ParseFrontmatter_ShouldParseTitleCorrectly(string fileContent, string expectedTitle) { // Arrange - var frontMatter = parser.ParseFrontmatterAndMarkdown(fileRelativePathCONST, fileFullPathCONST, fileContent); + var frontMatter = FrontMatter.Parse(fileRelativePathCONST, fileFullPathCONST, parser, fileContent); // Assert Assert.Equal(expectedTitle, frontMatter.Title); @@ -114,7 +114,7 @@ Date: 2023/01/01 var expectedDate = DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); // Act - var frontMatter = parser.ParseFrontmatterAndMarkdown(fileRelativePathCONST, fileFullPathCONST, fileContent); + var frontMatter = FrontMatter.Parse(fileRelativePathCONST, fileFullPathCONST, parser, fileContent); // Assert Assert.Equal(expectedDate, frontMatter.Date); @@ -130,7 +130,7 @@ Date: 2023/01/01 var expectedExpiryDate = DateTime.Parse("2024-06-01", CultureInfo.InvariantCulture); // Act - var frontMatter = parser.ParseFrontmatterAndMarkdown(fileRelativePathCONST, fileFullPathCONST, pageContent); + var frontMatter = FrontMatter.Parse(fileRelativePathCONST, fileFullPathCONST, parser, pageContent); // Assert Assert.Equal("Test Title", frontMatter.Title); @@ -142,7 +142,7 @@ Date: 2023/01/01 } [Fact] - public void ParseFrontmatter_ShouldThrowException_WhenInvalidYAMLSyntax() + public void ParseFrontmatter_ShouldThrowFormatException_WhenInvalidYAMLSyntax() { // Arrange const string fileContent = @"--- @@ -151,7 +151,8 @@ Title "; // Assert - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(fileRelativePathCONST, fileFullPathCONST, fileContent)); + Assert.Throws(() => + FrontMatter.Parse(fileRelativePathCONST, fileFullPathCONST, parser, fileContent)); } [Fact] @@ -172,7 +173,8 @@ Title public void ParseParams_ShouldFillParamsWithNonMatchingFields() { // Arrange - var page = new Page(parser.ParseFrontmatterAndMarkdown(string.Empty, string.Empty, pageContent), site); + var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, parser, pageContent); + var page = new Page(frontMatter, site); // Assert Assert.False(page.Params.ContainsKey("customParam")); @@ -184,7 +186,7 @@ Title { // Arrange var date = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); - var frontMatter = parser.ParseFrontmatterAndMarkdown(string.Empty, string.Empty, pageContent); + var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, parser, pageContent); Page page = new(frontMatter, site); // Act @@ -198,7 +200,7 @@ Title public void ParseFrontmatter_ShouldCreateTags() { // Arrange - var frontMatter = parser.ParseFrontmatterAndMarkdown(string.Empty, string.Empty, pageContent); + var frontMatter = FrontMatter.Parse(string.Empty, string.Empty, parser, pageContent); Page page = new(frontMatter, site); // Act @@ -209,39 +211,26 @@ Title } [Fact] - public void ParseFrontmatter_ShouldThrowExceptionWhenSiteIsNull() + public void FrontMatterParse_RawContentNull() { - _ = Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(null!, "fakeFilePath")); + _ = Assert.Throws(() => FrontMatter.Parse("invalidFrontmatter", "", "fakePath", "fakePath", frontMatterParser)); } [Fact] - public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathIsNull() - { - _ = Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(null!)); - } - - [Fact] - public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathDoesNotExist() - { - _ = Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile("fakePath")); - } - - [Fact] - public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathDoesNotExist2() + public void ParseYAML_ShouldThrowExceptionWhenFrontmatterIsInvalid() { - _ = Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(null!, null!, "fakeContent")); + _ = Assert.Throws(() => parser.Parse("invalidFrontmatter")); } [Fact] - public void ParseFrontmatter_ShouldHandleEmptyFileContent() + public void ParseYAML_ShouldSplitTheMetadata() { - _ = Assert.Throws(() => parser.ParseFrontmatterAndMarkdown("fakeFilePath", "/fakeFilePath", string.Empty)); - } + // Act + var (metadata, rawContent) = parser.SplitFrontMatter(pageContent); - [Fact] - public void ParseYAML_ShouldThrowExceptionWhenFrontmatterIsInvalid() - { - _ = Assert.Throws(() => parser.ParseFrontmatterAndMarkdown("fakeFilePath", "/fakeFilePath", "invalidFrontmatter")); + // Assert + Assert.Equal(pageFrontmaterCONST.TrimEnd(), metadata); + Assert.Equal(pageMarkdownCONST, rawContent); } [Fact] @@ -256,16 +245,6 @@ Title Assert.Equal("https://www.example.com/", siteSettings.BaseURL); } - [Fact] - public void ParseSiteSettings_ShouldReturnContent() - { - // Arrange - var frontMatter = parser.ParseFrontmatterAndMarkdown("fakeFilePath", "/fakeFilePath", pageContent); - - // Assert - Assert.Equal(pageMarkdownCONST, frontMatter.RawContent); - } - [Fact] public void SiteParams_ShouldHandleEmptyContent()