From 5b534ba0149ad5b816660e62d3e9491d0bcae69c Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Mon, 13 May 2024 14:00:56 -0500 Subject: [PATCH 1/3] feat: yaml parsing is now case-insensitive --- Tests/Parser/YAMLParserTests.cs | 69 +++++++++++++++++++++++++++++++-- source/Parsers/YAMLParser.cs | 41 ++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/Tests/Parser/YAMLParserTests.cs b/Tests/Parser/YAMLParserTests.cs index c63fdd3..cce9959 100644 --- a/Tests/Parser/YAMLParserTests.cs +++ b/Tests/Parser/YAMLParserTests.cs @@ -1,6 +1,6 @@ +using System.Globalization; using SuCoS.Helpers; using SuCoS.Models; -using System.Globalization; using SuCoS.Parsers; using Xunit; @@ -37,14 +37,12 @@ public class YamlParserTests : TestSetup """; private const string PageMarkdownConst = """ - # Real Data Test This is a test using real data. Real Data Test """; private const string SiteContentConst = """ - Title: My Site BaseURL: https://www.example.com/ Description: Tastiest C# Static Site Generator of the World @@ -282,4 +280,69 @@ public class YamlParserTests : TestSetup Assert.Equal(Expected, ((Dictionary)siteSettings.Params["ParamsNestedData"])["Level2"]); Assert.Equal("Test", ((siteSettings.Params["ParamsNestedData"] as Dictionary)?["Level2"] as List)?[0]); } + + [Theory] + [InlineData(""" + --- + Title: title-test + --- + + """)] + [InlineData(""" + --- + title: title-test + --- + + """)] + [InlineData(""" + --- + tiTle: title-test + --- + + """)] + [InlineData(""" + --- + tiTle: title-test-old + Title: title-test # the last on is used + --- + + """)] + public void FrontMatter_ShouldIgnoreCase(string fileContent) + { + // Arrange + var frontMatter = FrontMatter.Parse(FileRelativePathConst, FileFullPathConst, _parser, fileContent); + + // Assert + Assert.Equal("title-test", frontMatter.Title); + } + + [Theory] + [InlineData(""" + Title: title-test + BaseURL: https://www.example.com/ + """)] + [InlineData(""" + title: title-test + baseurl: https://www.example.com/ + """)] + [InlineData(""" + tiTle: title-test + baseUrl: https://www.example.com/ + """)] + [InlineData(""" + tiTle: title-test-old + Title: title-test # the last on is used + baseurl: https://www.example2.com/ + BaseURL: https://www.example.com/ # the last on is used + """)] + public void SiteSettings_ShouldIgnoreCase(string fileContent) + { + // Arrange + var siteSettings = _parser.Parse(fileContent); + Site = new Site(GenerateOptionsMock, siteSettings, FrontMatterParser, LoggerMock, SystemClockMock); + + // Assert + Assert.Equal("title-test", Site.Title); + Assert.Equal("https://www.example.com/", Site.BaseURL); + } } diff --git a/source/Parsers/YAMLParser.cs b/source/Parsers/YAMLParser.cs index 133d978..e8b9e19 100644 --- a/source/Parsers/YAMLParser.cs +++ b/source/Parsers/YAMLParser.cs @@ -1,3 +1,4 @@ +using System.Runtime.Serialization; using System.Text; using FolkerKinzel.Strings; using YamlDotNet.Serialization; @@ -21,9 +22,49 @@ public class YamlParser : IMetadataParser { _deserializer = new StaticDeserializerBuilder(new StaticAotContext()) .WithTypeConverter(new ParamsConverter()) + .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance) + .WithTypeInspector(n => new IgnoreCaseTypeInspector(n)) .IgnoreUnmatchedProperties() .Build(); } + private class IgnoreCaseTypeInspector(ITypeInspector innerTypeInspector) + : ITypeInspector + { + private readonly ITypeInspector _innerTypeInspector = innerTypeInspector ?? throw new ArgumentNullException(nameof(innerTypeInspector)); + + public IEnumerable GetProperties(Type type, object? container) + { + return _innerTypeInspector.GetProperties(type, container ?? null); + } + + public IPropertyDescriptor GetProperty(Type type, object? container, string name, bool ignoreUnmatched) + { + var candidates = GetProperties(type, container) + .Where(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)).ToList(); + + var property = candidates.FirstOrDefault(); + + if (property == null) + { + if (ignoreUnmatched) + { + return null; + } + + throw new SerializationException($"Property '{name}' not found on type '{type.FullName}'."); + } + + if (candidates.Count > 1) + { + throw new SerializationException( + $"Multiple properties with the name/alias '{name}' already exists on type '{type.FullName}', maybe you're misusing YamlAlias or maybe you are using the wrong naming convention? The matching properties are: {string.Join(", ", candidates.Select(p => p.Name))}" + ); + } + + return property; + } + } + /// public T Parse(string content) -- GitLab From 076a1def66aec738b0866804ab11dc78f82063de Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Mon, 13 May 2024 14:16:26 -0500 Subject: [PATCH 2/3] refactor: rename some site settings and front matter properties' names --- Tests/Models/FrontMatterTests.cs | 4 ++-- Tests/Models/PageTests.cs | 6 +++--- Tests/Parser/YAMLParserTests.cs | 18 ++++++++++-------- source/Commands/BuildCommand.cs | 2 +- source/Commands/NewSiteCommand.cs | 2 +- source/Models/FrontMatter.cs | 4 ++-- source/Models/IFrontMatter.cs | 4 ++-- source/Models/ISiteSettings.cs | 4 ++-- source/Models/Page.cs | 4 ++-- source/Models/Site.cs | 6 +++--- source/Models/SiteSettings.cs | 4 ++-- 11 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Tests/Models/FrontMatterTests.cs b/Tests/Models/FrontMatterTests.cs index 657a15c..7336a7c 100644 --- a/Tests/Models/FrontMatterTests.cs +++ b/Tests/Models/FrontMatterTests.cs @@ -18,14 +18,14 @@ public class FrontMatterTests : TestSetup Title = title, Section = section, Type = type, - URL = url + Url = url }; // Assert Assert.Equal(title, basicContent.Title); Assert.Equal(section, basicContent.Section); Assert.Equal(type, basicContent.Type); - Assert.Equal(url, basicContent.URL); + Assert.Equal(url, basicContent.Url); } [Theory] diff --git a/Tests/Models/PageTests.cs b/Tests/Models/PageTests.cs index d3d0516..42dec2b 100644 --- a/Tests/Models/PageTests.cs +++ b/Tests/Models/PageTests.cs @@ -1,7 +1,7 @@ +using System.Globalization; using NSubstitute; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; -using System.Globalization; using Xunit; namespace Tests.Models; @@ -61,7 +61,7 @@ word03 word04 word05 6 7 [eight](https://example.com) Assert.Equal(string.Empty, page.Section); Assert.Equal(Kind.Single, page.Kind); Assert.Equal("page", page.Type); - Assert.Null(page.URL); + Assert.Null(page.Url); Assert.Empty(page.Params); Assert.Null(page.Date); Assert.Null(page.LastMod); @@ -235,7 +235,7 @@ word03 word04 word05 6 7 [eight](https://example.com) { Title = TitleConst, SourceRelativePath = SourcePathConst, - URL = urlTemplate + Url = urlTemplate }, Site); var actualPermalink = page.CreatePermalink(); diff --git a/Tests/Parser/YAMLParserTests.cs b/Tests/Parser/YAMLParserTests.cs index cce9959..ef67089 100644 --- a/Tests/Parser/YAMLParserTests.cs +++ b/Tests/Parser/YAMLParserTests.cs @@ -177,7 +177,7 @@ public class YamlParserTests : TestSetup // Assert Assert.Equal("My Site", siteSettings.Title); - Assert.Equal("https://www.example.com/", siteSettings.BaseURL); + Assert.Equal("https://www.example.com/", siteSettings.BaseUrl); Assert.Equal("Tastiest C# Static Site Generator of the World", siteSettings.Description); Assert.Equal("Copyright message", siteSettings.Copyright); } @@ -255,7 +255,7 @@ public class YamlParserTests : TestSetup // Assert Assert.NotNull(siteSettings); Assert.Equal("My Site", siteSettings.Title); - Assert.Equal("https://www.example.com/", siteSettings.BaseURL); + Assert.Equal("https://www.example.com/", siteSettings.BaseUrl); } @@ -285,27 +285,28 @@ public class YamlParserTests : TestSetup [InlineData(""" --- Title: title-test + Url: my-page --- - """)] [InlineData(""" --- title: title-test + url: my-page --- - """)] [InlineData(""" --- tiTle: title-test + URL: my-page --- - """)] [InlineData(""" --- tiTle: title-test-old - Title: title-test # the last on is used + title: title-test # the last on is used + Url: my-page + url: my-page --- - """)] public void FrontMatter_ShouldIgnoreCase(string fileContent) { @@ -314,6 +315,7 @@ public class YamlParserTests : TestSetup // Assert Assert.Equal("title-test", frontMatter.Title); + Assert.Equal("my-page", frontMatter.Url); } [Theory] @@ -343,6 +345,6 @@ public class YamlParserTests : TestSetup // Assert Assert.Equal("title-test", Site.Title); - Assert.Equal("https://www.example.com/", Site.BaseURL); + Assert.Equal("https://www.example.com/", Site.BaseUrl); } } diff --git a/source/Commands/BuildCommand.cs b/source/Commands/BuildCommand.cs index 1ad4bb1..5530d7c 100644 --- a/source/Commands/BuildCommand.cs +++ b/source/Commands/BuildCommand.cs @@ -61,7 +61,7 @@ public class BuildCommand : BaseGeneratorCommand if (output is IPage page) { - var path = (url + (Site.UglyURLs ? string.Empty : "/index.html")).TrimStart('/'); + var path = (url + (Site.UglyUrLs ? string.Empty : "/index.html")).TrimStart('/'); // Generate the output path var outputAbsolutePath = Path.Combine(_options.Output, path); diff --git a/source/Commands/NewSiteCommand.cs b/source/Commands/NewSiteCommand.cs index 0d7dd1e..876153a 100644 --- a/source/Commands/NewSiteCommand.cs +++ b/source/Commands/NewSiteCommand.cs @@ -28,7 +28,7 @@ public sealed class NewSiteCommand(NewSiteOptions options, ILogger logger, IFile { Title = options.Title, Description = options.Description, - BaseURL = options.BaseUrl + BaseUrl = options.BaseUrl }; var site = new Site(new GenerateOptions() { SourceOption = options.Output }, _siteSettings, new YamlParser(), null!, null); diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs index 6f8587d..1df4b36 100644 --- a/source/Models/FrontMatter.cs +++ b/source/Models/FrontMatter.cs @@ -20,7 +20,7 @@ public class FrontMatter : IFrontMatter public string? Type { get; set; } = "page"; /// - public string? URL { get; set; } + public string? Url { get; set; } /// public bool? Draft { get; set; } @@ -66,7 +66,7 @@ public class FrontMatter : IFrontMatter /// [YamlIgnore] - public string? SourceRelativePathDirectory => (Path.GetDirectoryName(SourceRelativePath) ?? string.Empty) + public string SourceRelativePathDirectory => (Path.GetDirectoryName(SourceRelativePath) ?? string.Empty) .Replace('\\', '/'); /// diff --git a/source/Models/IFrontMatter.cs b/source/Models/IFrontMatter.cs index 29e704e..24b007f 100644 --- a/source/Models/IFrontMatter.cs +++ b/source/Models/IFrontMatter.cs @@ -44,7 +44,7 @@ public interface IFrontMatter : IParams, IFile /// /// will try to convert page.Parent.Title and page.Title. /// - string? URL { get; } + string? Url { get; } /// /// True for draft content. It will not be rendered unless @@ -81,7 +81,7 @@ public interface IFrontMatter : IParams, IFile /// A List of secondary URL patterns to be used to create the url. /// List URL, it will be parsed as liquid templates, so you can use page variables. /// - /// + /// List? Aliases { get; } /// diff --git a/source/Models/ISiteSettings.cs b/source/Models/ISiteSettings.cs index 2bbda14..8dd2248 100644 --- a/source/Models/ISiteSettings.cs +++ b/source/Models/ISiteSettings.cs @@ -23,10 +23,10 @@ public interface ISiteSettings : IParams /// /// The base URL that will be used to build public links. /// - public string BaseURL { get; } + public string BaseUrl { get; } /// /// The appearance of a URL is either ugly or pretty. /// - public bool UglyURLs { get; } + public bool UglyUrLs { get; } } diff --git a/source/Models/Page.cs b/source/Models/Page.cs index 83f04f3..0ec9e60 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -22,7 +22,7 @@ public class Page : IPage public string? Type => _frontMatter.Type; /// - public string? URL => _frontMatter.URL; + public string? Url => _frontMatter.Url; /// public bool? Draft => _frontMatter.Draft; @@ -318,7 +318,7 @@ endif var permalink = string.Empty; - urlForce ??= URL ?? (isIndex ? UrlForIndex : UrlForNonIndex); + urlForce ??= Url ?? (isIndex ? UrlForIndex : UrlForNonIndex); try { diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 0fdacaf..96f5a27 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -40,10 +40,10 @@ public class Site : ISite public string? Copyright => _settings.Copyright; /// - public string BaseURL => _settings.BaseURL; + public string BaseUrl => _settings.BaseUrl; /// - public bool UglyURLs => _settings.UglyURLs; + public bool UglyUrLs => _settings.UglyUrLs; #endregion SiteSettings @@ -236,7 +236,7 @@ public class Site : ISite SourceFullPath = Urlizer.Path(Path.Combine(SourceContentPath, relativePath, IndexLeafFileConst)), Title = title, Type = kind == Kind.Home ? "index" : sectionName, - URL = relativePath + Url = relativePath }; IPage? parent = null; diff --git a/source/Models/SiteSettings.cs b/source/Models/SiteSettings.cs index e24f897..af28478 100644 --- a/source/Models/SiteSettings.cs +++ b/source/Models/SiteSettings.cs @@ -23,7 +23,7 @@ public class SiteSettings : ISiteSettings /// - public string BaseURL { get; set; } = string.Empty; + public string BaseUrl { get; set; } = string.Empty; /// public Dictionary> Outputs { get; set; } = []; @@ -43,7 +43,7 @@ public class SiteSettings : ISiteSettings /// /// The appearance of a URL is either ugly or pretty. /// - public bool UglyURLs { get; set; } + public bool UglyUrLs { get; set; } #region IParams -- GitLab From 334b996050404169f1a585426ab828e46540b8bc Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Mon, 13 May 2024 14:41:56 -0500 Subject: [PATCH 3/3] chore: CHANGELOG.md --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2431ac4..7aac50a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. [Unreleased] +- `term.liquid` and `taxonomy.liquid` are now available for theming +- Added: YAML front matter and site settings are now case-insensitive + v[4.0.2] 2024-05-09 - Fixed crashes on serve @@ -104,8 +107,8 @@ v[1.0.0] 2023-07-15 - Born to be Wild - Added First Commit! [Unreleased]: https://gitlab.com/sucos/sucos/-/compare/v4.0.2...HEAD -[4.0.0]: https://gitlab.com/sucos/sucos/-/compare/v4.0.1...v4.0.2 -[4.0.0]: https://gitlab.com/sucos/sucos/-/compare/v4.0.0...v4.0.1 +[4.0.2]: https://gitlab.com/sucos/sucos/-/compare/v4.0.1...v4.0.2 +[4.0.1]: https://gitlab.com/sucos/sucos/-/compare/v4.0.0...v4.0.1 [4.0.0]: https://gitlab.com/sucos/sucos/-/compare/v3.0.0...v4.0.0 [3.0.0]: https://gitlab.com/sucos/sucos/-/compare/v2.4.0...v3.0.0 [2.4.0]: https://gitlab.com/sucos/sucos/-/compare/v2.3.0...v2.4.0 -- GitLab