From 385f2cc7df4422858ddd3dda8b649316a43c485c Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Tue, 20 Jun 2023 15:36:32 -0300 Subject: [PATCH 1/6] fix: site.HomePage detection --- source/BaseGeneratorCommand.cs | 4 ++-- source/ServeCommand.cs | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/source/BaseGeneratorCommand.cs b/source/BaseGeneratorCommand.cs index 40feb65..8ce9388 100644 --- a/source/BaseGeneratorCommand.cs +++ b/source/BaseGeneratorCommand.cs @@ -260,7 +260,7 @@ public abstract class BaseGeneratorCommand site.RegularPages.Add(frontmatter); frontmatter.Permalink = "/" + CreatePermalink(file.filePath, site, frontmatter); - if (site.HomePage is null && frontmatter.SourceFileNameWithoutExtension == "index") + if (site.HomePage is null && frontmatter.SourcePath == "index.md") { site.HomePage = frontmatter; frontmatter.Kind = Kind.index; @@ -343,7 +343,7 @@ public abstract class BaseGeneratorCommand baseGeneratorCommand: this, title: site.Title, site: site, - sourcePath: Path.Combine(relativePath, "index"), + sourcePath: Path.Combine(relativePath, "/index.md"), sourceFileNameWithoutExtension: "index", sourcePathDirectory: null ) diff --git a/source/ServeCommand.cs b/source/ServeCommand.cs index 62a62bd..5db45bd 100644 --- a/source/ServeCommand.cs +++ b/source/ServeCommand.cs @@ -126,11 +126,6 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable if (url != null) { _ = pages.TryAdd(url, frontmatter); - if (Path.GetFileName(url) == "index.html") - { - var path = Path.GetDirectoryName(url); - _ = pages.TryAdd(path!, frontmatter); - } } else { -- GitLab From 91dc1e6fe8c18cd489b0eef2d6931bfbfba4d3fd Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Tue, 20 Jun 2023 22:14:23 -0300 Subject: [PATCH 2/6] feat: content Params --- source/BaseGeneratorCommand.cs | 62 +++++++++++++++++++++++++++++++++- source/Models/Frontmatter.cs | 4 ++- source/Models/IParams.cs | 11 ++++++ source/Models/Site.cs | 4 ++- source/Parser/YAMLParser.cs | 11 ++++++ 5 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 source/Models/IParams.cs diff --git a/source/BaseGeneratorCommand.cs b/source/BaseGeneratorCommand.cs index 8ce9388..1c87530 100644 --- a/source/BaseGeneratorCommand.cs +++ b/source/BaseGeneratorCommand.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Fluid; +using Fluid.Values; using Markdig; using Microsoft.Extensions.FileProviders; using Serilog; @@ -63,7 +64,7 @@ public abstract class BaseGeneratorCommand protected readonly object syncLock = new(); /// - /// The template options. + /// The Fluid/Liquid template options. /// protected readonly TemplateOptions templateOptions = new(); @@ -212,6 +213,7 @@ public abstract class BaseGeneratorCommand templateOptions.MemberAccessStrategy.Register(); templateOptions.MemberAccessStrategy.Register(); templateOptions.FileProvider = new PhysicalFileProvider(Path.GetFullPath(site.SourceThemePath)); + templateOptions.Filters.AddFilter("whereParams", WhereParamsFilter); // Configure Markdig with the Bibliography extension markdownPipeline = new MarkdownPipelineBuilder() @@ -620,4 +622,62 @@ public abstract class BaseGeneratorCommand tagFrontmatterCache.Clear(); IgnoreCacheBefore = DateTime.Now; } + + /// + /// Fluid/Liquid filter to navigate Params dictionary + /// + /// + /// + /// + /// + /// + public static ValueTask WhereParamsFilter(FluidValue input, FilterArguments arguments, TemplateContext context) + { + if (input is null) + { + throw new ArgumentNullException(nameof(input)); + } + List result = new(); + var list = (input as ArrayValue)!.Values; + + var keys = arguments.At(0).ToStringValue().Split('.'); + foreach (var item in list) + { + if (item.ToObjectValue() is IParams param && CheckValueInDictionary(keys, param.Params, arguments.At(1).ToStringValue())) + { + result.Add(item); + } + } + + return new ValueTask(new ArrayValue((IEnumerable)result)); + } + + static bool CheckValueInDictionary(string[] array, Dictionary dictionary, string value) + { + var key = array[0]; + + // Check if the key exists in the dictionary + if (dictionary.TryGetValue(key, out var dictionaryValue)) + { + // If it's the last element in the array, check if the dictionary value matches the value parameter + if (array.Length == 1) + { + return dictionaryValue.Equals(value); + } + + // Check if the value is another dictionary + else if (dictionaryValue is Dictionary nestedDictionary) + { + // Create a new array without the current key + var newArray = new string[array.Length - 1]; + Array.Copy(array, 1, newArray, 0, newArray.Length); + + // Recursively call the method with the nested dictionary and the new array + return CheckValueInDictionary(newArray, nestedDictionary, value); + } + } + + // If the key doesn't exist or the value is not a dictionary, return false + return false; + } } diff --git a/source/Models/Frontmatter.cs b/source/Models/Frontmatter.cs index 58a328b..28539f8 100644 --- a/source/Models/Frontmatter.cs +++ b/source/Models/Frontmatter.cs @@ -9,7 +9,7 @@ namespace SuCoS; /// /// The meta data about each content Markdown file. /// -public class Frontmatter +public class Frontmatter : IParams { /// /// The content Title. @@ -94,6 +94,8 @@ public class Frontmatter } } + public Dictionary Params { get; set; } = new(); + /// /// Raw content, from the Markdown file. /// diff --git a/source/Models/IParams.cs b/source/Models/IParams.cs new file mode 100644 index 0000000..e5bdb69 --- /dev/null +++ b/source/Models/IParams.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace SuCoS; + +public interface IParams +{ + /// + /// Other non-standard values + /// + Dictionary Params { get; set; } +} \ No newline at end of file diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 8bad961..eb0b0ae 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -6,7 +6,7 @@ namespace SuCoS.Models; /// /// The main configuration of the program, primarily extracted from the app.yaml file. /// -public class Site +public class Site : IParams { /// /// Site Title. @@ -67,4 +67,6 @@ public class Site /// List of all content to be scanned and processed. /// public List<(string filePath, string content)> RawPages { get; set; } = new(); + + public Dictionary Params { get; set; } = new(); } diff --git a/source/Parser/YAMLParser.cs b/source/Parser/YAMLParser.cs index d53544a..9e113e8 100644 --- a/source/Parser/YAMLParser.cs +++ b/source/Parser/YAMLParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using SuCoS.Models; using YamlDotNet.Serialization; @@ -92,6 +93,16 @@ public partial class YAMLParser : IFrontmatterParser } } } + + foreach (var key in frontmatterDictionary.Keys.Cast()) + { + // If the property is not a standard Frontmatter property + if (typeof(Frontmatter).GetProperty(key) == null) + { + // Add it to Params + frontmatter.Params[key] = frontmatterDictionary[key]; + } + } foreach (var tagName in tags) { -- GitLab From a53af17420ff88d44816a4523d2df9afec92704a Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 21 Jun 2023 12:35:50 -0300 Subject: [PATCH 3/6] feat: site Params --- source/BaseGeneratorCommand.cs | 23 +++++---- source/Models/Frontmatter.cs | 1 + source/Models/IParams.cs | 6 ++- source/Models/Site.cs | 1 + source/Parser/IFrontmatterParser.cs | 2 +- source/Parser/YAMLParser.cs | 78 +++++++++++++++++++++-------- source/ServeCommand.cs | 2 +- 7 files changed, 79 insertions(+), 34 deletions(-) diff --git a/source/BaseGeneratorCommand.cs b/source/BaseGeneratorCommand.cs index 1c87530..f2375ab 100644 --- a/source/BaseGeneratorCommand.cs +++ b/source/BaseGeneratorCommand.cs @@ -198,7 +198,7 @@ public abstract class BaseGeneratorCommand try { - site = ReadAppConfig(options: options, frontmatterParser); + site = ParseSiteSettings(options: options, frontmatterParser); if (site is null) { throw new FormatException("Error reading app config"); @@ -302,7 +302,7 @@ public abstract class BaseGeneratorCommand /// The generate options. /// The frontmatter parser. /// The site configuration. - protected static Site ReadAppConfig(IGenerateOptions options, IFrontmatterParser frontmatterParser) + protected static Site ParseSiteSettings(IGenerateOptions options, IFrontmatterParser frontmatterParser) { if (options is null) { @@ -314,18 +314,18 @@ public abstract class BaseGeneratorCommand } // Read the main configation - var configFilePath = Path.Combine(options.Source, configFile); - if (!File.Exists(configFilePath)) + var filePath = Path.Combine(options.Source, configFile); + if (!File.Exists(filePath)) { throw new FileNotFoundException($"The {configFile} file was not found in the specified source directory: {options.Source}"); } - var configFileContent = File.ReadAllText(configFilePath); - var config = frontmatterParser.ParseAppConfig(configFileContent); - config.SourcePath = options.Source; - config.OutputPath = options.Output; + var fileContent = File.ReadAllText(filePath); + var settings = frontmatterParser.ParseSiteSettings(fileContent); + settings.SourcePath = options.Source; + settings.OutputPath = options.Output; - return config; + return settings; } /// @@ -637,6 +637,11 @@ public abstract class BaseGeneratorCommand { throw new ArgumentNullException(nameof(input)); } + if (arguments is null) + { + throw new ArgumentNullException(nameof(arguments)); + } + List result = new(); var list = (input as ArrayValue)!.Values; diff --git a/source/Models/Frontmatter.cs b/source/Models/Frontmatter.cs index 28539f8..6ddb9ac 100644 --- a/source/Models/Frontmatter.cs +++ b/source/Models/Frontmatter.cs @@ -94,6 +94,7 @@ public class Frontmatter : IParams } } + /// public Dictionary Params { get; set; } = new(); /// diff --git a/source/Models/IParams.cs b/source/Models/IParams.cs index e5bdb69..4045f9a 100644 --- a/source/Models/IParams.cs +++ b/source/Models/IParams.cs @@ -2,10 +2,14 @@ using System.Collections.Generic; namespace SuCoS; +/// +/// Interface for all classes that will implement a catch-all YAML +/// values. +/// public interface IParams { /// - /// Other non-standard values + /// Recursive dictionary with non-standard values /// Dictionary Params { get; set; } } \ No newline at end of file diff --git a/source/Models/Site.cs b/source/Models/Site.cs index eb0b0ae..82067a8 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -68,5 +68,6 @@ public class Site : IParams /// public List<(string filePath, string content)> RawPages { get; set; } = new(); + /// public Dictionary Params { get; set; } = new(); } diff --git a/source/Parser/IFrontmatterParser.cs b/source/Parser/IFrontmatterParser.cs index 5f6554a..64a48dc 100644 --- a/source/Parser/IFrontmatterParser.cs +++ b/source/Parser/IFrontmatterParser.cs @@ -22,5 +22,5 @@ public interface IFrontmatterParser /// /// /// - Site ParseAppConfig(string configFileContent); + Site ParseSiteSettings(string configFileContent); } \ No newline at end of file diff --git a/source/Parser/YAMLParser.cs b/source/Parser/YAMLParser.cs index 9e113e8..ff32564 100644 --- a/source/Parser/YAMLParser.cs +++ b/source/Parser/YAMLParser.cs @@ -14,8 +14,24 @@ namespace SuCoS.Parser; /// public partial class YAMLParser : IFrontmatterParser { + [GeneratedRegex(@"^---\s*[\r\n](?.*?)[\r\n]---\s*", RegexOptions.Singleline)] - private partial Regex regex(); + private partial Regex YAMLRegex(); + + /// + /// YamlDotNet parser, strictly set to allow automatically parse only known fields + /// + readonly IDeserializer yamlDeserializerRigid = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + /// + /// YamlDotNet parser to loosely parse the YAML file. Used to include all non-matching fields + /// into Params. + /// + readonly IDeserializer yamlDeserializer = new DeserializerBuilder() + .Build(); /// public Frontmatter? ParseFrontmatter(Site site, string filePath, ref string fileContent, BaseGeneratorCommand frontmatterManager) @@ -26,16 +42,15 @@ public partial class YAMLParser : IFrontmatterParser } Frontmatter? frontmatter = null; - var match = regex().Match(fileContent); + var match = YAMLRegex().Match(fileContent); if (match.Success) { - var frontmatterString = match.Groups["frontmatter"].Value; + var content = match.Groups["frontmatter"].Value; fileContent = fileContent[match.Length..].TrimStart('\n'); // Parse the front matter string into Frontmatter properties - var yamlDeserializer = new DeserializerBuilder().Build(); - var yamlObject = yamlDeserializer.Deserialize(new StringReader(frontmatterString)); + var yamlObject = yamlDeserializer.Deserialize(new StringReader(content)); if (yamlObject is Dictionary frontmatterDictionary) { @@ -93,16 +108,8 @@ public partial class YAMLParser : IFrontmatterParser } } } - - foreach (var key in frontmatterDictionary.Keys.Cast()) - { - // If the property is not a standard Frontmatter property - if (typeof(Frontmatter).GetProperty(key) == null) - { - // Add it to Params - frontmatter.Params[key] = frontmatterDictionary[key]; - } - } + + ParseParams(frontmatter, typeof(Frontmatter), content); foreach (var tagName in tags) { @@ -129,13 +136,40 @@ public partial class YAMLParser : IFrontmatterParser } /// - public Site ParseAppConfig(string configFileContent) + public Site ParseSiteSettings(string content) + { + var settings = yamlDeserializerRigid.Deserialize(content); + ParseParams(settings, typeof(Site), content); + return settings; + } + + /// + /// Parse all YAML files for non-matching fields. + /// + /// Site or Frontmatter object, that implements IParams + /// The type (Site or Frontmatter) + /// YAML content + void ParseParams(IParams settings, Type type, string content) { - var deserializer = new DeserializerBuilder() - .WithNamingConvention(PascalCaseNamingConvention.Instance) - .IgnoreUnmatchedProperties() - .Build(); - var config = deserializer.Deserialize(configFileContent); - return config; + var yamlObject = yamlDeserializer.Deserialize(new StringReader(content)); + if (yamlObject is Dictionary yamlDictionary) + { + foreach (var key in yamlDictionary.Keys.Cast()) + { + // If the property is not a standard Frontmatter property + if (type.GetProperty(key) == null) + { + // Recursively create a dictionary structure for the value + if (yamlDictionary[key] is Dictionary valueDictionary) + { + settings.Params[key] = valueDictionary; + } + else + { + settings.Params[key] = yamlDictionary[key]; + } + } + } + } } } diff --git a/source/ServeCommand.cs b/source/ServeCommand.cs index 5db45bd..dc7fd09 100644 --- a/source/ServeCommand.cs +++ b/source/ServeCommand.cs @@ -98,7 +98,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable { try { - site = ReadAppConfig(options: options, frontmatterParser); + site = ParseSiteSettings(options: options, frontmatterParser); if (site is null) { throw new FormatException("Error reading app config"); -- GitLab From 0f548951ae5a7b5101a70faed1a04ccf91125631 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 21 Jun 2023 12:36:24 -0300 Subject: [PATCH 4/6] feat: set the Current version into the executable --- .build.Nuke/Build.Compile.cs | 1 - .build.Nuke/Build.GitLab.cs | 4 ++-- .build.Nuke/Build.Publish.cs | 4 +++- .build.Nuke/Build.Version.cs | 10 ++++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.build.Nuke/Build.Compile.cs b/.build.Nuke/Build.Compile.cs index ea4253c..718ee69 100644 --- a/.build.Nuke/Build.Compile.cs +++ b/.build.Nuke/Build.Compile.cs @@ -2,7 +2,6 @@ using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tools.DotNet; using Nuke.Common.Utilities.Collections; -using Serilog; using static Nuke.Common.Tools.DotNet.DotNetTasks; namespace SuCoS; diff --git a/.build.Nuke/Build.GitLab.cs b/.build.Nuke/Build.GitLab.cs index 5ee4872..69fc805 100644 --- a/.build.Nuke/Build.GitLab.cs +++ b/.build.Nuke/Build.GitLab.cs @@ -49,13 +49,13 @@ sealed partial class Build : NukeBuild .Executes(async () => { // The package name constructed using packageName, runtimeIdentifier, and Version - var package = $"{packageName}-{runtimeIdentifier}-{CurrentVersion}"; + var package = $"{packageName}-{runtimeIdentifier}-{CurrentTag}"; // The filename of the package, constructed using the package variable var filename = $"{package}.zip"; // The URL for the package in the GitLab generic package registry - var packageLink = GitLabAPIUrl($"packages/generic/{packageName}/{CurrentVersion}/{filename}"); + var packageLink = GitLabAPIUrl($"packages/generic/{packageName}/{CurrentTag}/{filename}"); // Create the zip package var fullpath = Path.GetFullPath(filename); diff --git a/.build.Nuke/Build.Publish.cs b/.build.Nuke/Build.Publish.cs index add5c9e..13bf9e7 100644 --- a/.build.Nuke/Build.Publish.cs +++ b/.build.Nuke/Build.Publish.cs @@ -1,7 +1,6 @@ using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tools.DotNet; -using Serilog; using static Nuke.Common.Tools.DotNet.DotNetTasks; namespace SuCoS; @@ -42,6 +41,9 @@ sealed partial class Build : NukeBuild .SetPublishSingleFile(publishSingleFile) .SetPublishTrimmed(publishTrimmed) .SetAuthors("Bruno Massa") + .SetVersion(CurrentVersion) + .SetAssemblyVersion(CurrentVersion) + .SetInformationalVersion(CurrentVersion) ); }); } diff --git a/.build.Nuke/Build.Version.cs b/.build.Nuke/Build.Version.cs index 7bdc3f9..a87d89b 100644 --- a/.build.Nuke/Build.Version.cs +++ b/.build.Nuke/Build.Version.cs @@ -22,7 +22,7 @@ sealed partial class Build : NukeBuild string VersionMajor => $"{gitVersion.Major}"; - string VersionMajorMinor => $"{gitVersion.Major}.{gitVersion.Minor}"; + string VersionMajorMinor => $"{gitVersion.Major}.{gitVersion.Minor}"; /// /// The version in a format that can be used as a tag. @@ -35,7 +35,7 @@ sealed partial class Build : NukeBuild bool HasNewCommits => gitVersion.CommitsSinceVersionSource != "0"; string currentVersion; - string CurrentVersion + string CurrentTag { get { @@ -43,6 +43,7 @@ sealed partial class Build : NukeBuild return currentVersion; } } + string CurrentVersion => CurrentTag.TrimStart('v'); /// /// Prints the current version. @@ -52,8 +53,9 @@ sealed partial class Build : NukeBuild { var lastCommmit = GitTasks.Git("log -1").FirstOrDefault().Text; var status = GitTasks.Git("status").FirstOrDefault().Text; - Log.Information("Current version: {Version}", CurrentVersion); - Log.Information($"GitVersion before = {Version}"); + Log.Information("Current version: {Version}", CurrentVersion); + Log.Information("Current tag: {Version}", CurrentTag); + Log.Information("Next version: {Version}", Version); }); /// -- GitLab From 4d58f091bf804d3217d7bb780a8593086d98dd4b Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 21 Jun 2023 12:36:55 -0300 Subject: [PATCH 5/6] doc: include latest release and pipeline status on README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 3304dbe..aaf0239 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,15 @@ The vision is to be a close substitute of [Hugo](https://gohugo.io/), but writte +[![Latest release](https://gitlab.com/sucos/sucos/-/badges/release.svg)](https://gitlab.com/sucos/sucos) +![Pipepline](https://gitlab.com/sucos/sucos/badges/main/pipeline.svg?ignore_skipped=true) + ## Usage First, navigate to the **SuCoS** folder. Chris Kibble Then, run the following command: + ```bash SuCoS --source YOUR_SITE_PATH ``` -- GitLab From b3e534744d4d7781d87da7cad55923fd58a74ff7 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 21 Jun 2023 13:00:06 -0300 Subject: [PATCH 6/6] fix: Params dictionary back --- source/Parser/YAMLParser.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/Parser/YAMLParser.cs b/source/Parser/YAMLParser.cs index ff32564..8389b59 100644 --- a/source/Parser/YAMLParser.cs +++ b/source/Parser/YAMLParser.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Serilog; using SuCoS.Models; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -14,7 +15,7 @@ namespace SuCoS.Parser; /// public partial class YAMLParser : IFrontmatterParser { - + [GeneratedRegex(@"^---\s*[\r\n](?.*?)[\r\n]---\s*", RegexOptions.Singleline)] private partial Regex YAMLRegex(); @@ -152,7 +153,7 @@ public partial class YAMLParser : IFrontmatterParser void ParseParams(IParams settings, Type type, string content) { var yamlObject = yamlDeserializer.Deserialize(new StringReader(content)); - if (yamlObject is Dictionary yamlDictionary) + if (yamlObject is Dictionary yamlDictionary) { foreach (var key in yamlDictionary.Keys.Cast()) { -- GitLab