From 2affdde1a8fbe8b94dffcbfe488a4c6d67c27025 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Mon, 3 Jul 2023 16:30:41 -0300 Subject: [PATCH] refactor: 150+ code quality changes pointed by Qodana --- .build.Nuke/Build.Container.cs | 4 +- .build.Nuke/Build.Test.cs | 2 +- .build.Nuke/_build_nuke.csproj.DotSettings | 3 +- source/BaseGeneratorCommand.cs | 54 +++++---- source/BuildOptions.cs | 2 +- source/Helpers/FileUtils.cs | 59 +++++----- source/Helpers/SiteHelper.cs | 121 +++++++++++---------- source/Helpers/StopwatchReporter.cs | 7 +- source/Helpers/Urlizer.cs | 25 ++--- source/Models/Frontmatter.cs | 72 ++++++------ source/Models/IParams.cs | 2 +- source/Models/Site.cs | 66 +++++------ source/Parser/YAMLParser.cs | 80 +++++++------- source/Program.cs | 14 +-- source/ServeCommand.cs | 38 ++++--- source/ServeOptions.cs | 2 +- source/wwwroot/js/reload.js | 4 +- test/BaseGeneratorCommandTests.cs | 21 +--- test/Helpers/UrlizerTests.cs | 12 +- test/Models/BasicContentTests.cs | 11 +- test/Models/FrontmatterTests.cs | 29 +++-- test/Models/SiteTests.cs | 15 ++- test/Parser/YAMLParserTests.cs | 119 ++++++++++---------- 23 files changed, 361 insertions(+), 401 deletions(-) diff --git a/.build.Nuke/Build.Container.cs b/.build.Nuke/Build.Container.cs index 418369b..45db07a 100644 --- a/.build.Nuke/Build.Container.cs +++ b/.build.Nuke/Build.Container.cs @@ -27,7 +27,7 @@ sealed partial class Build : NukeBuild .OnlyWhenStatic(() => runtimeIdentifier != "win-x64") .Executes(() => { - var tagsOriginal = new string[] { "latest", Version, VersionMajorMinor, VersionMajor }; + var tagsOriginal = new[] { "latest", Version, VersionMajorMinor, VersionMajor }; var tags = tagsOriginal.Select(tag => $"{runtimeIdentifier}-{tag}").ToList(); if (containerDefaultRID == runtimeIdentifier) { @@ -39,7 +39,7 @@ sealed partial class Build : NukeBuild .SetPath(PublishDirectory) .SetFile($"./Dockerfile") .SetTag(tags.Select(tag => $"{ContainerRegistryImage}:{tag}").ToArray()) - .SetBuildArg(new string[] { $"BASE_IMAGE={BaseImage}", $"COPY_PATH={PublishDirectory}" }) + .SetBuildArg(new[] { $"BASE_IMAGE={BaseImage}", $"COPY_PATH={PublishDirectory}" }) .SetProcessLogger((outputType, output) => { // A bug this log type value diff --git a/.build.Nuke/Build.Test.cs b/.build.Nuke/Build.Test.cs index c1d0c37..8ed7a10 100644 --- a/.build.Nuke/Build.Test.cs +++ b/.build.Nuke/Build.Test.cs @@ -40,7 +40,7 @@ sealed partial class Build : NukeBuild .SetFormat(CoverletOutputFormat.cobertura)); }); - public Target TestReport => _ => _ + Target TestReport => _ => _ .DependsOn(Test) .Executes(() => { diff --git a/.build.Nuke/_build_nuke.csproj.DotSettings b/.build.Nuke/_build_nuke.csproj.DotSettings index c8947fc..7bc2848 100644 --- a/.build.Nuke/_build_nuke.csproj.DotSettings +++ b/.build.Nuke/_build_nuke.csproj.DotSettings @@ -1,4 +1,4 @@ - + DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW @@ -20,6 +20,7 @@ True True True + True True True True diff --git a/source/BaseGeneratorCommand.cs b/source/BaseGeneratorCommand.cs index 0231d33..76c17b0 100644 --- a/source/BaseGeneratorCommand.cs +++ b/source/BaseGeneratorCommand.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Fluid; using Fluid.Values; using Serilog; -using SuCoS.Helper; +using SuCoS.Helpers; using SuCoS.Models; using SuCoS.Parser; @@ -38,7 +38,7 @@ public abstract class BaseGeneratorCommand /// /// The logger (Serilog). /// - protected ILogger logger; + protected readonly ILogger logger; /// /// Initializes a new instance of the class. @@ -51,11 +51,8 @@ public abstract class BaseGeneratorCommand { throw new ArgumentNullException(nameof(options)); } - if (logger is null) - { - throw new ArgumentNullException(nameof(logger)); - } - this.logger = logger; + + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); stopwatch = new(logger); logger.Information("Source path: {source}", propertyValue: options.Source); @@ -71,7 +68,7 @@ public abstract class BaseGeneratorCommand /// /// /// - public static ValueTask WhereParamsFilter(FluidValue input, FilterArguments arguments, TemplateContext context) + protected static ValueTask WhereParamsFilter(FluidValue input, FilterArguments arguments, TemplateContext context) { if (input is null) { @@ -94,35 +91,36 @@ public abstract class BaseGeneratorCommand } } - return new ValueTask(new ArrayValue((IEnumerable)result)); + return new ArrayValue(result); } - static bool CheckValueInDictionary(string[] array, Dictionary dictionary, string value) + private static bool CheckValueInDictionary(string[] array, IReadOnlyDictionary dictionary, string value) { var key = array[0]; - // Check if the key exists in the dictionary - if (dictionary.TryGetValue(key, out var dictionaryValue)) + // If the key doesn't exist or the value is not a dictionary, return false + 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); - } + return false; + } - // 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); + // 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); + } - // Recursively call the method with the nested dictionary and the new array - return CheckValueInDictionary(newArray, nestedDictionary, value); - } + // Check if the value is another dictionary + if (dictionaryValue is not Dictionary nestedDictionary) + { + return false; } - // If the key doesn't exist or the value is not a dictionary, return false - return false; + // 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); } } diff --git a/source/BuildOptions.cs b/source/BuildOptions.cs index 571fdfb..4a7ec02 100644 --- a/source/BuildOptions.cs +++ b/source/BuildOptions.cs @@ -12,5 +12,5 @@ public class BuildOptions : IGenerateOptions public string Output { get; set; } = "./public"; /// - public bool Future { get; set; } = false; + public bool Future { get; set; } } diff --git a/source/Helpers/FileUtils.cs b/source/Helpers/FileUtils.cs index d69f404..3fbf30f 100644 --- a/source/Helpers/FileUtils.cs +++ b/source/Helpers/FileUtils.cs @@ -4,7 +4,7 @@ using System.IO; using System.Linq; using SuCoS.Models; -namespace SuCoS.Helper; +namespace SuCoS.Helpers; /// /// Helper methods for scanning files. @@ -17,7 +17,7 @@ public static class FileUtils /// The directory path. /// The initial directory path. /// The list of Markdown file paths. - public static List GetAllMarkdownFiles(string directory, string? basePath = null) + public static IEnumerable GetAllMarkdownFiles(string directory, string? basePath = null) { basePath ??= directory; var files = Directory.GetFiles(directory, "*.md").ToList(); @@ -55,16 +55,16 @@ public static class FileUtils var cache = isBaseTemplate ? site.baseTemplateCache : site.contentTemplateCache; // Check if the template content is already cached - if (!cache.TryGetValue(index, out var content)) + if (cache.TryGetValue(index, out var content)) + return content; + + var templatePaths = GetTemplateLookupOrder(themePath, frontmatter, isBaseTemplate); + content = GetTemplate(templatePaths); + + // Cache the template content for future use + lock (cache) { - var templatePaths = GetTemplateLookupOrder(themePath, frontmatter, isBaseTemplate); - content = GetTemplate(templatePaths); - - // Cache the template content for future use - lock (cache) - { - _ = cache.TryAdd(index, content); - } + _ = cache.TryAdd(index, content); } return content; @@ -83,12 +83,9 @@ public static class FileUtils } // Iterate through the template paths and return the content of the first existing file - foreach (var templatePath in templatePaths) + foreach (var templatePath in templatePaths.Where(File.Exists)) { - if (File.Exists(templatePath)) - { - return File.ReadAllText(templatePath); - } + return File.ReadAllText(templatePath); } return string.Empty; @@ -101,7 +98,7 @@ public static class FileUtils /// The frontmatter to determine the template index. /// Indicates whether the template is a base template. /// The list of template paths in the lookup order. - public static List GetTemplateLookupOrder(string themePath, Frontmatter frontmatter, bool isBaseTemplate) + private static List GetTemplateLookupOrder(string themePath, Frontmatter frontmatter, bool isBaseTemplate) { if (frontmatter is null) { @@ -109,23 +106,17 @@ public static class FileUtils } // Generate the lookup order for template files based on the theme path, frontmatter section, type, and kind - var sections = frontmatter.Section is not null ? new string[] { frontmatter.Section.ToString(), string.Empty } : new string[] { string.Empty }; - var types = new string[] { frontmatter.Type.ToString(), "_default" }; + var sections = frontmatter.Section is not null ? new[] { frontmatter.Section, string.Empty } : new[] { string.Empty }; + var types = new[] { frontmatter.Type, "_default" }; var kinds = isBaseTemplate - ? new string[] { frontmatter.Kind.ToString() + "-baseof", "baseof" } - : new string[] { frontmatter.Kind.ToString() }; - var templatePaths = new List(); - foreach (var section in sections) - { - foreach (var type in types) - { - foreach (var kind in kinds) - { - var path = Path.Combine(themePath, section, type, kind) + ".liquid"; - templatePaths.Add(path); - } - } - } - return templatePaths; + ? new[] { frontmatter.Kind + "-baseof", "baseof" } + : new[] { frontmatter.Kind.ToString() }; + + // for each section, each type and each kind + return (from section in sections + from type in types + from kind in kinds + let path = Path.Combine(themePath, section, type!, kind) + ".liquid" + select path).ToList(); } } diff --git a/source/Helpers/SiteHelper.cs b/source/Helpers/SiteHelper.cs index ecaa537..0207a5f 100644 --- a/source/Helpers/SiteHelper.cs +++ b/source/Helpers/SiteHelper.cs @@ -6,13 +6,73 @@ using Serilog; using SuCoS.Models; using SuCoS.Parser; -namespace SuCoS.Helper; +namespace SuCoS.Helpers; /// /// Helper methods for scanning files. /// public static class SiteHelper { + /// + /// Creates the pages dictionary. + /// + /// + public static Site Init(string configFile, IGenerateOptions options, IFrontmatterParser frontmatterParser, FilterDelegate whereParamsFilter, ILogger logger, StopwatchReporter stopwatch) + { + if (stopwatch is null) + { + throw new ArgumentNullException(nameof(stopwatch)); + } + + Site site; + try + { + site = ParseSettings(configFile, options, frontmatterParser, whereParamsFilter, logger); + } + catch + { + throw new FormatException("Error reading app config"); + } + + site.ResetCache(); + + // Scan content files + var markdownFiles = FileUtils.GetAllMarkdownFiles(site.SourceContentPath); + site.ContentPaths.AddRange(markdownFiles); + + site.ParseSourceFiles(stopwatch); + + site.TemplateOptions.FileProvider = new PhysicalFileProvider(Path.GetFullPath(site.SourceThemePath)); + + return site; + } + + /// + /// Get the section name from a file path + /// + /// + /// + public static string GetSection(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + return string.Empty; + } + + // Split the path into individual folders + var folders = filePath.Split(Path.DirectorySeparatorChar); + + // Retrieve the first folder + foreach (var folder in folders) + { + if (!string.IsNullOrEmpty(folder)) + { + return folder; + } + } + + return string.Empty; + } /// /// Reads the application settings. @@ -23,7 +83,7 @@ public static class SiteHelper /// The method to be used in the whereParams. /// The logger instance. Injectable for testing /// The site settings. - public static Site ParseSettings(string configFile, IGenerateOptions options, IFrontmatterParser frontmatterParser, FilterDelegate whereParamsFilter, ILogger logger) + private static Site ParseSettings(string configFile, IGenerateOptions options, IFrontmatterParser frontmatterParser, FilterDelegate whereParamsFilter, ILogger logger) { if (options is null) { @@ -66,61 +126,4 @@ public static class SiteHelper throw new FormatException("Error reading app config"); } } - - - /// - /// Creates the pages dictionary. - /// - /// - public static Site Init(string configFile, IGenerateOptions options, IFrontmatterParser frontmatterParser, FilterDelegate whereParamsFilter, ILogger logger, StopwatchReporter stopwatch) - { - if (stopwatch is null) - { - throw new ArgumentNullException(nameof(stopwatch)); - } - - Site site; - try - { - site = SiteHelper.ParseSettings(configFile, options, frontmatterParser, whereParamsFilter, logger); - } - catch - { - throw new FormatException("Error reading app config"); - } - - site.ResetCache(); - - // Scan content files - var markdownFiles = FileUtils.GetAllMarkdownFiles(site.SourceContentPath); - site.ContentPaths.AddRange(markdownFiles); - - site.ParseSourceFiles(stopwatch); - - site.TemplateOptions.FileProvider = new PhysicalFileProvider(Path.GetFullPath(site!.SourceThemePath)); - - return site; - } - - /// - /// Get the section name from a file path - /// - /// - /// - public static string GetSection(string filePath) - { - // Split the path into individual folders - var folders = filePath?.Split(Path.DirectorySeparatorChar); - - // Retrieve the first folder - for (var i = 0; i < folders?.Length; i++) - { - if (!string.IsNullOrEmpty(folders[i])) - { - return folders[i]; - } - } - - return string.Empty; - } } diff --git a/source/Helpers/StopwatchReporter.cs b/source/Helpers/StopwatchReporter.cs index 5a691f7..46219a6 100644 --- a/source/Helpers/StopwatchReporter.cs +++ b/source/Helpers/StopwatchReporter.cs @@ -5,7 +5,7 @@ using System.Globalization; using System.Linq; using Serilog; -namespace SuCoS.Helper; +namespace SuCoS.Helpers; /// /// This class is used to report the time taken to execute @@ -70,11 +70,8 @@ public class StopwatchReporter ("Step", "Status", "Duration", 0) }; - foreach (var stepEntry in stopwatches) + foreach (var (stepName, stopwatch) in stopwatches) { - var stepName = stepEntry.Key; - var stopwatch = stepEntry.Value; - // var itemCount = itemCounts.ContainsKey(stepName) ? itemCounts[stepName] : 0; _ = itemCounts.TryGetValue(stepName, out var itemCount); var duration = stopwatch.ElapsedMilliseconds; var durationString = $"{duration} ms"; diff --git a/source/Helpers/Urlizer.cs b/source/Helpers/Urlizer.cs index 715e0c0..1abc0ac 100644 --- a/source/Helpers/Urlizer.cs +++ b/source/Helpers/Urlizer.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text.RegularExpressions; -namespace SuCoS.Helper; +namespace SuCoS.Helpers; /// /// Helper class to convert a string to a URL-friendly string. @@ -23,10 +23,9 @@ public static partial class Urlizer /// /// /// - public static string Urlize(string title, UrlizerOptions? options = null) + public static string Urlize(string? title, UrlizerOptions? options = null) { title ??= ""; - options ??= new UrlizerOptions(); // Use default options if not provided var cleanedTitle = !options.LowerCase ? title : title.ToLower(CultureInfo.CurrentCulture); @@ -48,19 +47,19 @@ public static partial class Urlizer /// /// /// - public static string UrlizePath(string path, UrlizerOptions? options = null) + public static string UrlizePath(string? path, UrlizerOptions? options = null) { - var pathString = (path ?? string.Empty); - var items = pathString.Split("/"); + path ??= string.Empty; + var items = path.Split("/"); var result = new List(); - for (var i = 0; i < items.Length; i++) + foreach (var item in items) { - if (!string.IsNullOrEmpty(items[i])) + if (!string.IsNullOrEmpty(item)) { - result.Add(Urlize(items[i], options)); + result.Add(Urlize(item, options)); } } - return (pathString.StartsWith('/') ? '/' : string.Empty) + string.Join('/', result); + return (path.StartsWith('/') ? '/' : string.Empty) + string.Join('/', result); } } @@ -73,16 +72,16 @@ public class UrlizerOptions /// /// Force to generate lowercase URLs. /// - public bool LowerCase { get; set; } = true; + public bool LowerCase { get; init; } = true; /// /// The character that will be used to replace spaces and other invalid characters. /// - public char? ReplacementChar { get; set; } = '-'; + public char? ReplacementChar { get; init; } = '-'; /// /// Replace dots with the replacement character. /// Note that it will break file paths and domain names. /// - public bool ReplaceDot { get; set; } + public bool ReplaceDot { get; init; } } diff --git a/source/Models/Frontmatter.cs b/source/Models/Frontmatter.cs index 466ea35..4de58bb 100644 --- a/source/Models/Frontmatter.cs +++ b/source/Models/Frontmatter.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using Fluid; using Markdig; -using SuCoS.Helper; +using SuCoS.Helpers; using YamlDotNet.Serialization; namespace SuCoS.Models; @@ -27,10 +27,10 @@ public class Frontmatter : IBaseContent, IParams public Kind Kind { get; set; } = Kind.single; /// - public string Type { get; set; } = "page"; + public string? Type { get; set; } = "page"; /// - public string? URL { get; set; } + public string? URL { get; init; } #endregion IBaseContent @@ -171,11 +171,12 @@ public class Frontmatter : IBaseContent, IParams { get { - if (contentCacheTime is null || Site.IgnoreCacheBefore > contentCacheTime) + if (contentCacheTime is not null && !(Site.IgnoreCacheBefore > contentCacheTime)) { - contentCache = CreateContent(); - contentCacheTime = clock.UtcNow; + return contentCache!; } + contentCache = CreateContent(); + contentCacheTime = clock.UtcNow; return contentCache!; } } @@ -184,7 +185,7 @@ public class Frontmatter : IBaseContent, IParams /// Other content that mention this content. /// Used to create the tags list and Related Posts section. /// - public List Pages + public IEnumerable Pages { get { @@ -193,16 +194,15 @@ public class Frontmatter : IBaseContent, IParams return new List(); } - if (pagesCached is null) + if (pagesCached is not null) { - pagesCached ??= new(); - foreach (var permalink in PagesReferences) - { - if (permalink is not null) - { - pagesCached.Add(Site.PagesDict[permalink]); - } - } + return pagesCached; + } + + pagesCached ??= new(); + foreach (var permalink in PagesReferences) + { + pagesCached.Add(Site.PagesDict[permalink]); } return pagesCached; } @@ -211,7 +211,7 @@ public class Frontmatter : IBaseContent, IParams /// /// List of pages from the content folder. /// - public List RegularPages + public IEnumerable RegularPages { get { @@ -234,13 +234,13 @@ public class Frontmatter : IBaseContent, IParams { urls.Add(Permalink); } + if (AliasesProcessed is not null) { - foreach (var aliases in AliasesProcessed) - { - urls.Add(aliases); - } + urls.AddRange(from aliases in AliasesProcessed + select aliases); } + return urls; } } @@ -349,9 +349,6 @@ public class Frontmatter : IBaseContent, IParams /// The processed output file content. public string CreateOutputFile() { - // Theme content - string result; - // Process the theme base template // If the theme base template file is available, parse and render the template using the frontmatter data // Otherwise, use the processed content as the final result @@ -360,21 +357,18 @@ public class Frontmatter : IBaseContent, IParams var fileContents = FileUtils.GetTemplate(Site.SourceThemePath, this, Site, true); if (string.IsNullOrEmpty(fileContents)) { - result = Content; + return Content; } - else if (Site.FluidParser.TryParse(fileContents, out var template, out var error)) + + if (Site.FluidParser.TryParse(fileContents, out var template, out var error)) { var context = new TemplateContext(Site.TemplateOptions); _ = context.SetValue("page", this); - result = template.Render(context); - } - else - { - Site.Logger?.Error("Error parsing theme template: {Error}", error); - return string.Empty; + return template.Render(context); } - return result; + Site.Logger?.Error("Error parsing theme template: {Error}", error); + return string.Empty; } /// @@ -389,7 +383,8 @@ public class Frontmatter : IBaseContent, IParams { return ContentPreRendered; } - else if (Site.FluidParser.TryParse(fileContents, out var template, out var error)) + + if (Site.FluidParser.TryParse(fileContents, out var template, out var error)) { var context = new TemplateContext(Site.TemplateOptions) .SetValue("page", this); @@ -404,10 +399,9 @@ public class Frontmatter : IBaseContent, IParams return string.Empty; } } - else - { - Site.Logger?.Error("Error parsing theme template: {Error}", error); - return string.Empty; - } + + Site.Logger?.Error("Error parsing theme template: {Error}", error); + return string.Empty; + } } diff --git a/source/Models/IParams.cs b/source/Models/IParams.cs index 4045f9a..ed73249 100644 --- a/source/Models/IParams.cs +++ b/source/Models/IParams.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace SuCoS; +namespace SuCoS.Models; /// /// Interface for all classes that will implement a catch-all YAML diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 3c59960..a3159fb 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Fluid; using Markdig; using Serilog; -using SuCoS.Helper; +using SuCoS.Helpers; using SuCoS.Parser; using YamlDotNet.Serialization; @@ -117,13 +117,13 @@ public class Site : IParams /// /// The frontmatter of the home page; /// - public Frontmatter? Home { get; set; } + public Frontmatter? Home { get; private set; } /// /// List of all content to be scanned and processed. /// [YamlIgnore] - public List ContentPaths { get; set; } = new(); + public List ContentPaths { get; } = new(); /// /// Command line options @@ -158,7 +158,7 @@ public class Site : IParams /// /// The time that the older cache should be ignored. /// - public DateTime IgnoreCacheBefore { get; set; } + public DateTime IgnoreCacheBefore { get; private set; } /// /// Datetime wrapper @@ -214,7 +214,7 @@ public class Site : IParams TemplateOptions.MemberAccessStrategy.Register(); TemplateOptions.MemberAccessStrategy.Register(); - this.Clock = clock; + Clock = clock; } /// @@ -244,8 +244,8 @@ public class Site : IParams throw new ArgumentNullException(nameof(originalFrontmatter)); } - var id = baseContent.URL ?? ""; - Frontmatter? frontmatter = null; + var id = baseContent.URL; + Frontmatter? frontmatter; lock (syncLock) { if (!automaticContentCache.TryGetValue(id, out frontmatter)) @@ -276,18 +276,14 @@ public class Site : IParams } // TODO: still too hardcoded - if (frontmatter.Type == "tags") + if (frontmatter.Type != "tags") + return frontmatter; + + lock (originalFrontmatter) { - lock (originalFrontmatter!) - { - if (frontmatter.Type == "tags") - { - originalFrontmatter.Tags ??= new(); - originalFrontmatter.Tags!.Add(frontmatter); - } - } + originalFrontmatter.Tags ??= new(); + originalFrontmatter.Tags!.Add(frontmatter); } - return frontmatter; } @@ -377,24 +373,21 @@ public class Site : IParams { if (!PagesDict.TryGetValue(frontmatter.Permalink, out var old) || overwrite) { - if (old is not null) + if (old?.PagesReferences is not null) { - if (old?.PagesReferences is not null) + frontmatter.PagesReferences ??= new(); + foreach (var page in old.PagesReferences) { - frontmatter.PagesReferences ??= new(); - foreach (var page in old.PagesReferences) - { - frontmatter.PagesReferences.Add(page); - } + frontmatter.PagesReferences.Add(page); } } if (frontmatter.Aliases is not null) { frontmatter.AliasesProcessed ??= new(); - for (var i = 0; i < frontmatter.Aliases.Count; i++) + foreach (var alias in frontmatter.Aliases) { - frontmatter.AliasesProcessed.Add(frontmatter.CreatePermalink(frontmatter.Aliases[i])); + frontmatter.AliasesProcessed.Add(frontmatter.CreatePermalink(alias)); } } @@ -407,17 +400,18 @@ public class Site : IParams } // Create a section page when due - if (frontmatter.Type != "section" - && !string.IsNullOrEmpty(frontmatter.Permalink) - && !string.IsNullOrEmpty(frontmatter.Section)) + if (frontmatter.Type == "section" + || string.IsNullOrEmpty(frontmatter.Permalink) + || string.IsNullOrEmpty(frontmatter.Section)) { - var contentTemplate = new BasicContent( - title: frontmatter.Section, - section: "section", - type: "section", - url: frontmatter.Section - ); - CreateAutomaticFrontmatter(contentTemplate, frontmatter); + return; } + var contentTemplate = new BasicContent( + title: frontmatter.Section, + section: "section", + type: "section", + url: frontmatter.Section + ); + CreateAutomaticFrontmatter(contentTemplate, frontmatter); } } diff --git a/source/Parser/YAMLParser.cs b/source/Parser/YAMLParser.cs index 897c271..ae35946 100644 --- a/source/Parser/YAMLParser.cs +++ b/source/Parser/YAMLParser.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using SuCoS.Helper; +using SuCoS.Helpers; using SuCoS.Models; using YamlDotNet.Serialization; @@ -12,12 +12,12 @@ namespace SuCoS.Parser; /// /// Responsible for parsing the content frontmatter using YAML /// -public partial class YAMLParser : IFrontmatterParser +public class YAMLParser : IFrontmatterParser { /// /// YamlDotNet parser, strictly set to allow automatically parse only known fields /// - readonly IDeserializer yamlDeserializerRigid = new DeserializerBuilder() + private readonly IDeserializer yamlDeserializerRigid = new DeserializerBuilder() .IgnoreUnmatchedProperties() .Build(); @@ -25,11 +25,11 @@ public partial class YAMLParser : IFrontmatterParser /// YamlDotNet parser to loosely parse the YAML file. Used to include all non-matching fields /// into Params. /// - readonly IDeserializer yamlDeserializer = new DeserializerBuilder() + private readonly IDeserializer yamlDeserializer = new DeserializerBuilder() .Build(); /// - public Frontmatter? ParseFrontmatterAndMarkdownFromFile(Site site, in string filePath, in string? sourceContentPath = null) + public Frontmatter ParseFrontmatterAndMarkdownFromFile(Site site, in string filePath, in string? sourceContentPath = null) { if (site is null) { @@ -56,7 +56,7 @@ public partial class YAMLParser : IFrontmatterParser } /// - public Frontmatter? ParseFrontmatterAndMarkdown(Site site, in string fileRelativePath, in string fileContent) + public Frontmatter ParseFrontmatterAndMarkdown(Site site, in string fileRelativePath, in string fileContent) { if (site is null) { @@ -90,7 +90,7 @@ public partial class YAMLParser : IFrontmatterParser private Frontmatter ParseYAML(ref Site site, in string filePath, string yaml, in string rawContent) { var page = yamlDeserializerRigid.Deserialize(new StringReader(yaml)) ?? throw new FormatException("Error parsing frontmatter"); - var sourceFileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath) ?? string.Empty; + var sourceFileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); var section = SiteHelper.GetSection(filePath); page.RawContent = rawContent; page.Section = section; @@ -102,24 +102,24 @@ public partial class YAMLParser : IFrontmatterParser page.Type ??= section; var yamlObject = yamlDeserializer.Deserialize(new StringReader(yaml)); - if (yamlObject is Dictionary yamlDictionary) + if (yamlObject is not Dictionary yamlDictionary || !(yamlDictionary.TryGetValue("Tags", out var tags) && tags is List tagsValues)) { - if (yamlDictionary.TryGetValue("Tags", out var tags) && tags is List tagsValues) - { - foreach (var tagObj in tagsValues) - { - var tagName = (string)tagObj; - var contentTemplate = new BasicContent( - title: tagName, - section: "tags", - type: "tags", - url: "tags/" + Urlizer.Urlize(tagName) - ); - _ = site.CreateAutomaticFrontmatter(contentTemplate, page); - } - } - ParseParams(page, typeof(Frontmatter), yaml, yamlDictionary); + return page; } + + foreach (var tagObj in tagsValues) + { + var tagName = (string)tagObj; + var contentTemplate = new BasicContent( + title: tagName, + section: "tags", + type: "tags", + url: "tags/" + Urlizer.Urlize(tagName) + ); + _ = site.CreateAutomaticFrontmatter(contentTemplate, page); + } + ParseParams(page, typeof(Frontmatter), yaml, yamlDictionary); + return page; } @@ -150,23 +150,27 @@ public partial class YAMLParser : IFrontmatterParser } yamlObject ??= yamlDeserializer.Deserialize(new StringReader(yaml)); - if (yamlObject is Dictionary yamlDictionary) + if (yamlObject is not Dictionary yamlDictionary) + { + return; + } + + foreach (var key in yamlDictionary.Keys.Cast()) { - foreach (var key in yamlDictionary.Keys.Cast()) + // If the property is not a standard Frontmatter property + if (type.GetProperty(key) != null) + { + continue; + } + + // Recursively create a dictionary structure for the value + if (yamlDictionary[key] is Dictionary valueDictionary) + { + settings.Params[key] = valueDictionary; + } + else { - // 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]; - } - } + settings.Params[key] = yamlDictionary[key]; } } } diff --git a/source/Program.cs b/source/Program.cs index a609aad..055f529 100644 --- a/source/Program.cs +++ b/source/Program.cs @@ -16,7 +16,7 @@ public class Program /// /// Constructor /// - public Program(ILogger logger) + private Program(ILogger logger) { this.logger = logger; } @@ -56,14 +56,14 @@ public class Program // BuildCommand setup var buildOutputOption = new Option(new[] { "--output", "-o" }, () => "./public", "Output directory path"); - Command buildCommand = new("build", "Builds the site") + Command buildCommandHandler = new("build", "Builds the site") { sourceOption, buildOutputOption, futureOption, verboseOption }; - buildCommand.SetHandler((source, output, future, verbose) => + buildCommandHandler.SetHandler((source, output, future, verbose) => { BuildOptions buildOptions = new() { @@ -80,13 +80,13 @@ public class Program sourceOption, buildOutputOption, futureOption, verboseOption); // ServerCommand setup - Command serveCommand = new("serve", "Starts the server") + Command serveCommandHandler = new("serve", "Starts the server") { sourceOption, futureOption, verboseOption }; - serveCommand.SetHandler(async (source, future, verbose) => + serveCommandHandler.SetHandler(async (source, future, verbose) => { ServeOptions serverOptions = new() { @@ -106,8 +106,8 @@ public class Program RootCommand rootCommand = new("SuCoS commands") { - buildCommand, - serveCommand + buildCommandHandler, + serveCommandHandler }; return rootCommand.Invoke(args); diff --git a/source/ServeCommand.cs b/source/ServeCommand.cs index 42e24df..f78381d 100644 --- a/source/ServeCommand.cs +++ b/source/ServeCommand.cs @@ -7,9 +7,8 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; -using Microsoft.Extensions.DependencyInjection; using Serilog; -using SuCoS.Helper; +using SuCoS.Helpers; using SuCoS.Models; namespace SuCoS; @@ -19,6 +18,9 @@ namespace SuCoS; /// public class ServeCommand : BaseGeneratorCommand, IDisposable { + private const string baseURLDefault = "http://localhost"; + private const int portDefault = 1122; + /// /// The ServeOptions object containing the configuration parameters for the server. /// This includes settings such as the source directory to watch for file changes, @@ -62,7 +64,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable /// A boolean flag indicating whether a server restart is currently in progress. /// This is used to prevent overlapping restarts when file changes are detected in quick succession. /// - private volatile bool restartInProgress = false; + private volatile bool restartInProgress; /// /// A SemaphoreSlim used to ensure that server restarts due to file changes occur sequentially, @@ -89,7 +91,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable /// The base URL for the server. /// The port number for the server. /// A Task representing the asynchronous operation. - public async Task StartServer(string baseURL, int port) + private async Task StartServer(string baseURL, int port) { logger.Information("Starting server..."); @@ -118,7 +120,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable public void Dispose() { host?.Dispose(); - sourceFileWatcher?.Dispose(); + sourceFileWatcher.Dispose(); debounceTimer?.Dispose(); GC.SuppressFinalize(this); } @@ -131,9 +133,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable public ServeCommand(ServeOptions options, ILogger logger) : base(options, logger) { this.options = options ?? throw new ArgumentNullException(nameof(options)); - var baseURL = "http://localhost"; - var port = 1122; - baseURLGlobal = $"{baseURL}:{port}"; + baseURLGlobal = $"{baseURLDefault}:{portDefault}"; // Watch for file changes in the specified path sourceFileWatcher = StartFileWatcher(options.Source); @@ -199,7 +199,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable private async Task HandleRequest(HttpContext context) { var requestPath = context.Request.Path.Value; - if (string.IsNullOrEmpty(Path.GetExtension(context.Request.Path.Value)) && (requestPath.Length > 1)) + if (string.IsNullOrEmpty(Path.GetExtension(context.Request.Path.Value)) && requestPath.Length > 1) { requestPath = requestPath.TrimEnd('/'); } @@ -251,10 +251,10 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable return context.Response.WriteAsync(content); } - private async Task HandleStaticFileRequest(HttpContext context, string fileAbsolutePath) + private static async Task HandleStaticFileRequest(HttpContext context, string fileAbsolutePath) { context.Response.ContentType = GetContentType(fileAbsolutePath); - using var fileStream = new FileStream(fileAbsolutePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + await using var fileStream = new FileStream(fileAbsolutePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); await fileStream.CopyToAsync(context.Response.Body); } @@ -265,7 +265,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable await context.Response.WriteAsync(content); } - private async Task HandleNotFoundRequest(HttpContext context) + private static async Task HandleNotFoundRequest(HttpContext context) { context.Response.StatusCode = 404; await context.Response.WriteAsync("404 - File Not Found"); @@ -332,14 +332,16 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable debounceTimer?.Dispose(); debounceTimer = new Timer(async _ => { - if (!restartInProgress) + if (restartInProgress) { - logger.Information("File change detected: {FullPath}", e.FullPath); - - restartInProgress = true; - await RestartServer(); - restartInProgress = false; + return; } + + logger.Information("File change detected: {FullPath}", e.FullPath); + + restartInProgress = true; + await RestartServer(); + restartInProgress = false; }, null, TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan); } } diff --git a/source/ServeOptions.cs b/source/ServeOptions.cs index e1bb4d3..fe9d50d 100644 --- a/source/ServeOptions.cs +++ b/source/ServeOptions.cs @@ -12,5 +12,5 @@ public class ServeOptions : IGenerateOptions public string Output { get; set; } = "./public"; /// - public bool Future { get; set; } = false; + public bool Future { get; set; } } diff --git a/source/wwwroot/js/reload.js b/source/wwwroot/js/reload.js index 3b7fd5c..341ab08 100644 --- a/source/wwwroot/js/reload.js +++ b/source/wwwroot/js/reload.js @@ -11,14 +11,14 @@ var pingInterval = setInterval(function () { .then(function (timestamp) { if (currentTimestamp == null) { currentTimestamp = timestamp; - } else if (timestamp != currentTimestamp) { + } else if (timestamp !== currentTimestamp) { currentTimestamp = timestamp; clearInterval(pingInterval); showReloadWarning(); setTimeout(reloadPage, 100); } }) - .catch(function (error) { + .catch(function () { showStatusWarning(); }); }, 1000); diff --git a/test/BaseGeneratorCommandTests.cs b/test/BaseGeneratorCommandTests.cs index e65e419..0b86369 100644 --- a/test/BaseGeneratorCommandTests.cs +++ b/test/BaseGeneratorCommandTests.cs @@ -1,10 +1,9 @@ using System.Reflection; -using Fluid; -using Fluid.Values; using Serilog; +using SuCoS; using Xunit; -namespace SuCoS.Tests; +namespace Test; public class BaseGeneratorCommandTests { @@ -33,20 +32,6 @@ public class BaseGeneratorCommandTests Assert.Throws(() => new BaseGeneratorCommandStub(testOptions, null!)); } - [Fact] - public async Task WhereParamsFilter_ShouldThrowArgumentNullException_WhenInputIsNull() - { - await Assert.ThrowsAsync( - () => BaseGeneratorCommand.WhereParamsFilter(null!, new FilterArguments(), new TemplateContext()).AsTask()); - } - - [Fact] - public async Task WhereParamsFilter_ShouldThrowArgumentNullException_WhenArgumentsIsNull() - { - await Assert.ThrowsAsync( - () => BaseGeneratorCommand.WhereParamsFilter(new ArrayValue(new FluidValue[0]), null!, new TemplateContext()).AsTask()); - } - [Fact] public void CheckValueInDictionary_ShouldWorkCorrectly() { @@ -58,6 +43,6 @@ public class BaseGeneratorCommandTests var result = method.Invoke(null, parameters); Assert.NotNull(result); - Assert.True((bool)result!); + Assert.True((bool)result); } } diff --git a/test/Helpers/UrlizerTests.cs b/test/Helpers/UrlizerTests.cs index 09a1c9d..f51e6f2 100644 --- a/test/Helpers/UrlizerTests.cs +++ b/test/Helpers/UrlizerTests.cs @@ -1,7 +1,7 @@ using Xunit; -using SuCoS.Helper; +using SuCoS.Helpers; -namespace SuCoSTests; +namespace Test.Helpers; public class UrlizerTests { @@ -56,7 +56,7 @@ public class UrlizerTests [Fact] public void Urlize_WithoutOptions_ReturnsExpectedResult() { - var text = "Hello, World!"; + const string text = "Hello, World!"; var result = Urlizer.Urlize(text); Assert.Equal("hello-world", result); @@ -65,7 +65,7 @@ public class UrlizerTests [Fact] public void UrlizePath_WithoutOptions_ReturnsExpectedResult() { - var path = "Documents/My Report.docx"; + const string path = "Documents/My Report.docx"; var result = Urlizer.UrlizePath(path); Assert.Equal("documents/my-report.docx", result); @@ -74,7 +74,7 @@ public class UrlizerTests [Fact] public void Urlize_SpecialCharsInText_ReturnsOnlyHyphens() { - var text = "!@#$%^&*()"; + const string text = "!@#$%^&*()"; var result = Urlizer.Urlize(text); Assert.Equal("", result); @@ -83,7 +83,7 @@ public class UrlizerTests [Fact] public void UrlizePath_SpecialCharsInPath_ReturnsOnlyHyphens() { - var path = "/!@#$%^&*()/"; + const string path = "/!@#$%^&*()/"; var result = Urlizer.UrlizePath(path); Assert.Equal("/", result); diff --git a/test/Models/BasicContentTests.cs b/test/Models/BasicContentTests.cs index a492b60..1241e9e 100644 --- a/test/Models/BasicContentTests.cs +++ b/test/Models/BasicContentTests.cs @@ -1,7 +1,7 @@ -namespace SuCoS.Models.Tests; - -using Xunit; using SuCoS.Models; +using Xunit; + +namespace Test.Models; public class BasicContentTests { @@ -26,13 +26,12 @@ public class BasicContentTests public void Constructor_Sets_Kind_To_List_If_Not_Provided() { // Arrange - string title = "Title1", section = "Section1", type = "Type1", url = "URL1"; - var kind = Kind.list; + const string title = "Title1", section = "Section1", type = "Type1", url = "URL1"; // Act var basicContent = new BasicContent(title, section, type, url); // Assert - Assert.Equal(kind, basicContent.Kind); + Assert.Equal(Kind.list, basicContent.Kind); } } diff --git a/test/Models/FrontmatterTests.cs b/test/Models/FrontmatterTests.cs index 9a74113..d94e08b 100644 --- a/test/Models/FrontmatterTests.cs +++ b/test/Models/FrontmatterTests.cs @@ -1,24 +1,23 @@ using System.Globalization; using Moq; +using SuCoS; using SuCoS.Models; using Xunit; -namespace SuCoS.Tests; +namespace Test.Models; public class FrontmatterTests { private readonly ISystemClock clock; - private readonly Mock systemClockMock; - private readonly string title = "Test Title"; - private readonly string sourcePath = "/path/to/file.md"; + private readonly Mock systemClockMock = new(); private readonly Site site; + private const string titleCONST = "Test Title"; + private const string sourcePathCONST = "/path/to/file.md"; public FrontmatterTests() { - systemClockMock = new Mock(); var testDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); systemClockMock.Setup(c => c.Now).Returns(testDate); - clock = systemClockMock.Object; site = new(clock); } @@ -67,17 +66,17 @@ public class FrontmatterTests [Fact] public void Aliases_ShouldParseAsUrls() { - var frontmatter = new Frontmatter(title, sourcePath, site) + var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) { Title = "Title", - Aliases = new List() { "v123", "{{ page.Title }}" } + Aliases = new() { "v123", "{{ page.Title }}" } }; // Act site.PostProcessFrontMatter(frontmatter); // Assert - foreach (var url in new string[] { "/v123", "/title" }) + foreach (var url in new[] { "/v123", "/title" }) { site.PagesDict.TryGetValue(url, out var frontmatter1); Assert.NotNull(frontmatter1); @@ -90,7 +89,7 @@ public class FrontmatterTests [InlineData(1, false)] public void IsDateExpired_ShouldReturnExpectedResult(int days, bool expected) { - var frontmatter = new Frontmatter(title, sourcePath, site) + var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) { ExpiryDate = clock.Now.AddDays(days) }; @@ -107,7 +106,7 @@ public class FrontmatterTests [InlineData("2022-06-28", "2024-06-28", true)] public void IsDatePublishable_ShouldReturnCorrectValues(string? publishDate, string? date, bool expectedValue) { - var frontmatter = new Frontmatter(title, sourcePath, site) + var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) { PublishDate = publishDate is null ? null : DateTime.Parse(publishDate, CultureInfo.InvariantCulture), Date = date is null ? null : DateTime.Parse(date, CultureInfo.InvariantCulture) @@ -122,7 +121,7 @@ public class FrontmatterTests [InlineData(true, true)] public void IsValidDate_ShouldReturnExpectedResult(bool futureOption, bool expected) { - var frontmatter = new Frontmatter(title, sourcePath, site) + var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) { Date = clock.Now.AddDays(1) }; @@ -140,7 +139,7 @@ public class FrontmatterTests [InlineData("/another/path", "/another/path/test-title")] public void CreatePermalink_ShouldReturnCorrectUrl_WhenUrlIsNull(string sourcePathDirectory, string expectedUrl) { - var frontmatter = new Frontmatter(title, sourcePath, site) + var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) { SourcePathDirectory = sourcePathDirectory }; @@ -154,7 +153,7 @@ public class FrontmatterTests [InlineData("{{ page.Title }}/{{ page.SourceFileNameWithoutExtension }}", "/test-title/file")] public void Permalink_CreateWithDefaultOrCustomURLTemplate(string urlTemplate, string expectedPermalink) { - var frontmatter = new Frontmatter(title, sourcePath, site) + var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) { URL = urlTemplate }; @@ -169,7 +168,7 @@ public class FrontmatterTests [InlineData(Kind.list, false)] public void RegularPages_ShouldReturnCorrectPages_WhenKindIsSingle(Kind kind, bool isExpectedPage) { - var page = new Frontmatter(title, sourcePath, site) { Kind = kind }; + var page = new Frontmatter(titleCONST, sourcePathCONST, site) { Kind = kind }; // Act site.PostProcessFrontMatter(page); diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs index 402b278..da57db3 100644 --- a/test/Models/SiteTests.cs +++ b/test/Models/SiteTests.cs @@ -1,10 +1,10 @@ using Xunit; using Moq; -using SuCoS.Models; using System.Globalization; -using SuCoS.Helper; +using SuCoS.Helpers; +using SuCoS.Models; -namespace SuCoS.Tests; +namespace Test.Models; /// /// Unit tests for the Site class. @@ -12,16 +12,15 @@ namespace SuCoS.Tests; public class SiteTests { private readonly Site site; - private readonly Mock systemClockMock; - readonly string testSite1Path = ".TestSites/01"; + private readonly Mock systemClockMock = new(); + private const string testSite1PathCONST = ".TestSites/01"; // based on the compiled test.dll path // that is typically "bin/Debug/netX.0/test.dll" - readonly string testSitesPath = "../../.."; + private const string testSitesPath = "../../.."; public SiteTests() { - systemClockMock = new Mock(); var testDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); systemClockMock.Setup(c => c.Now).Returns(testDate); site = new Site(systemClockMock.Object); @@ -32,7 +31,7 @@ public class SiteTests [InlineData("test02.md")] public void Test_ScanAllMarkdownFiles(string fileName) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSite1Path)); + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSite1PathCONST)); // Act var ContentPaths = FileUtils.GetAllMarkdownFiles(Path.Combine(siteFullPath, "content")); diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs index 6815017..62dfc24 100644 --- a/test/Parser/YAMLParserTests.cs +++ b/test/Parser/YAMLParserTests.cs @@ -1,18 +1,18 @@ using Xunit; using Moq; -using SuCoS.Models; using SuCoS.Parser; using System.Globalization; -using SuCoS.Helper; +using SuCoS.Helpers; +using SuCoS.Models; -namespace SuCoS.Tests; +namespace Test.Parser; public class YAMLParserTests { private readonly YAMLParser parser; - private readonly Mock site; + private readonly Mock siteDefault; - private readonly string pageFrontmater = @"Title: Test Title + private const string pageFrontmaterCONST = @"Title: Test Title Type: post Date: 2023-07-01 LastMod: 2023-06-01 @@ -30,12 +30,12 @@ NestedData: - Real Data customParam: Custom Value "; - private readonly string pageMarkdown = @" + private const string pageMarkdownCONST = @" # Real Data Test This is a test using real data. Real Data Test "; - private readonly string siteContent = @" + private const string siteContentCONST = @" Title: My Site BaseUrl: https://www.example.com/ customParam: Custom Value @@ -44,16 +44,17 @@ NestedData: - Test - Real Data "; + private const string filePathCONST = "test.md"; private readonly string pageContent; public YAMLParserTests() { parser = new YAMLParser(); - site = new Mock(); + siteDefault = new Mock(); pageContent = @$"--- -{pageFrontmater} +{pageFrontmaterCONST} --- -{pageMarkdown}"; +{pageMarkdownCONST}"; } [Fact] @@ -79,20 +80,18 @@ Date: 2023-04-01 ", "")] public void ParseFrontmatter_ShouldParseTitleCorrectly(string fileContent, string expectedTitle) { - var filePath = "test.md"; - // Act - var frontmatter = parser.ParseFrontmatterAndMarkdown(site.Object, filePath, fileContent); + var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent); // Asset - Assert.Equal(expectedTitle, frontmatter?.Title); + Assert.Equal(expectedTitle, frontmatter.Title); } [Fact] public void ParseFrontmatter_ShouldThrowException_WhenSiteIsNull() { // Asset - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(null!, "test.md", pageContent)); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(null!, filePathCONST, pageContent)); } [Theory] @@ -106,68 +105,65 @@ Date: 2023/01/01 ", "2023-01-01")] public void ParseFrontmatter_ShouldParseDateCorrectly(string fileContent, string expectedDateString) { - var filePath = "test.md"; var expectedDate = DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); // Act - var frontmatter = parser.ParseFrontmatterAndMarkdown(site.Object, filePath, fileContent); + var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent); // Asset - Assert.Equal(expectedDate, frontmatter?.Date); + Assert.Equal(expectedDate, frontmatter.Date); } [Fact] public void ParseFrontmatter_ShouldParseOtherFieldsCorrectly() { - var filePath = "test.md"; var expectedDate = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); var expectedLastMod = DateTime.Parse("2023-06-01", CultureInfo.InvariantCulture); var expectedPublishDate = DateTime.Parse("2023-06-01", CultureInfo.InvariantCulture); var expectedExpiryDate = DateTime.Parse("2024-06-01", CultureInfo.InvariantCulture); // Act - var frontmatter = parser.ParseFrontmatterAndMarkdown(site.Object, filePath, pageContent); + var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, pageContent); // Asset - Assert.Equal("Test Title", frontmatter?.Title); - Assert.Equal("post", frontmatter?.Type); - Assert.Equal(expectedDate, frontmatter?.Date); - Assert.Equal(expectedLastMod, frontmatter?.LastMod); - Assert.Equal(expectedPublishDate, frontmatter?.PublishDate); - Assert.Equal(expectedExpiryDate, frontmatter?.ExpiryDate); + Assert.Equal("Test Title", frontmatter.Title); + Assert.Equal("post", frontmatter.Type); + Assert.Equal(expectedDate, frontmatter.Date); + Assert.Equal(expectedLastMod, frontmatter.LastMod); + Assert.Equal(expectedPublishDate, frontmatter.PublishDate); + Assert.Equal(expectedExpiryDate, frontmatter.ExpiryDate); } [Fact] public void ParseFrontmatter_ShouldThrowException_WhenInvalidYAMLSyntax() { - var fileContent = @"--- + const string fileContent = @"--- Title --- "; - var filePath = "test.md"; // Asset - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(site.Object, filePath, fileContent)); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent)); } [Fact] public void ParseSiteSettings_ShouldReturnSiteWithCorrectSettings() { // Act - var siteSettings = parser.ParseSiteSettings(siteContent); + var site = parser.ParseSiteSettings(siteContentCONST); // Asset - Assert.Equal("https://www.example.com/", siteSettings.BaseUrl); - Assert.Equal("My Site", siteSettings.Title); + Assert.Equal("https://www.example.com/", site.BaseUrl); + Assert.Equal("My Site", site.Title); } [Fact] public void ParseParams_ShouldFillParamsWithNonMatchingFields() { - var page = new Frontmatter("Test Title", "/test.md", site.Object); + var page = new Frontmatter("Test Title", "/test.md", siteDefault.Object); // Act - parser.ParseParams(page, typeof(Frontmatter), pageFrontmater); + parser.ParseParams(page, typeof(Frontmatter), pageFrontmaterCONST); // Asset Assert.True(page.Params.ContainsKey("customParam")); @@ -178,33 +174,33 @@ Title public void ParseFrontmatter_ShouldParseContentInSiteFolder() { var date = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); - var frontmatter = parser.ParseFrontmatterAndMarkdown(site.Object, "", pageContent); + var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "", pageContent); // Act - site.Object.PostProcessFrontMatter(frontmatter!); + siteDefault.Object.PostProcessFrontMatter(frontmatter); // Asset - Assert.Equal(date, frontmatter?.Date); + Assert.Equal(date, frontmatter.Date); } [Fact] public void ParseFrontmatter_ShouldCreateTags() { - var frontmatter = parser.ParseFrontmatterAndMarkdown(site.Object, "", pageContent); + var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "", pageContent); // Asset - Assert.Equal(2, frontmatter!.Tags?.Count); + Assert.Equal(2, frontmatter.Tags?.Count); } [Fact] public void ParseFrontmatter_ShouldParseCategoriesCorrectly() { - var frontmatter = parser.ParseFrontmatterAndMarkdown(site.Object, "fakeFilePath", pageContent); + var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", pageContent); // Asset - Assert.Equal(new[] { "Test", "Real Data" }, frontmatter?.Params["Categories"]); - Assert.Equal(new[] { "Test", "Real Data" }, (frontmatter?.Params["NestedData"] as Dictionary)?["Level2"]); - Assert.Equal("Test", ((frontmatter?.Params["NestedData"] as Dictionary)?["Level2"] as List)?[0]); + Assert.Equal(new[] { "Test", "Real Data" }, frontmatter.Params["Categories"]); + Assert.Equal(new[] { "Test", "Real Data" }, (frontmatter.Params["NestedData"] as Dictionary)?["Level2"]); + Assert.Equal("Test", ((frontmatter.Params["NestedData"] as Dictionary)?["Level2"] as List)?[0]); } [Fact] @@ -216,37 +212,37 @@ Title [Fact] public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathIsNull() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(site.Object, null!)); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(siteDefault.Object, null!)); } [Fact] public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathDoesNotExist() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(site.Object, "fakePath")); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(siteDefault.Object, "fakePath")); } [Fact] public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathDoesNotExist2() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(site.Object, null!, "fakeContent")); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, null!, "fakeContent")); } [Fact] public void ParseFrontmatter_ShouldHandleEmptyFileContent() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(site.Object, "fakeFilePath", "")); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", "")); } [Fact] public void ParseYAML_ShouldThrowExceptionWhenFrontmatterIsInvalid() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(site.Object, "fakeFilePath", "invalidFrontmatter")); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", "invalidFrontmatter")); } [Fact] public void ParseSiteSettings_ShouldReturnSiteSettings() { - var site = parser.ParseSiteSettings(siteContent); + var site = parser.ParseSiteSettings(siteContentCONST); Assert.NotNull(site); Assert.Equal("My Site", site.Title); Assert.Equal("https://www.example.com/", site.BaseUrl); @@ -255,39 +251,38 @@ Title [Fact] public void ParseSiteSettings_ShouldReturnContent() { - var frontmatter = parser.ParseFrontmatterAndMarkdown(site.Object, "fakeFilePath", pageContent); + var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", pageContent); - Assert.Equal(pageMarkdown, frontmatter?.RawContent); + Assert.Equal(pageMarkdownCONST, frontmatter.RawContent); } [Fact] public void SiteParams_ShouldThrowExceptionWhenSettingsIsNull() { - Assert.Throws(() => parser.ParseParams(null!, typeof(Site), siteContent)); + Assert.Throws(() => parser.ParseParams(null!, typeof(Site), siteContentCONST)); } [Fact] public void SiteParams_ShouldThrowExceptionWhenTypeIsNull() { - var site = new Site(); - Assert.Throws(() => parser.ParseParams(site, null!, siteContent)); + Assert.Throws(() => parser.ParseParams(siteDefault.Object, null!, siteContentCONST)); } [Fact] public void SiteParams_ShouldHandleEmptyContent() { - parser.ParseParams(site.Object, typeof(Site), string.Empty); - Assert.Empty(site.Object.Params); + parser.ParseParams(siteDefault.Object, typeof(Site), string.Empty); + Assert.Empty(siteDefault.Object.Params); } [Fact] public void SiteParams_ShouldPopulateParamsWithExtraFields() { - parser.ParseParams(site.Object, typeof(Site), siteContent); - Assert.NotEmpty(site.Object.Params); - Assert.True(site.Object.Params.ContainsKey("customParam")); - Assert.Equal("Custom Value", site.Object.Params["customParam"]); - Assert.Equal(new[] { "Test", "Real Data" }, ((Dictionary)site.Object.Params["NestedData"])["Level2"]); - Assert.Equal("Test", ((site.Object?.Params["NestedData"] as Dictionary)?["Level2"] as List)?[0]); + parser.ParseParams(siteDefault.Object, typeof(Site), siteContentCONST); + Assert.NotEmpty(siteDefault.Object.Params); + Assert.True(siteDefault.Object.Params.ContainsKey("customParam")); + Assert.Equal("Custom Value", siteDefault.Object.Params["customParam"]); + Assert.Equal(new[] { "Test", "Real Data" }, ((Dictionary)siteDefault.Object.Params["NestedData"])["Level2"]); + Assert.Equal("Test", ((siteDefault.Object?.Params["NestedData"] as Dictionary)?["Level2"] as List)?[0]); } } -- GitLab