From c474dd34fba96f7eef64ac4b98d0d724ab31b72b Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Mon, 10 Jul 2023 18:29:57 -0300 Subject: [PATCH 1/9] refactor: rename FrontMatter class to Page --- source/BaseGeneratorCommand.cs | 6 +- source/BuildCommand.cs | 4 +- source/Helpers/FileUtils.cs | 34 ++-- source/Helpers/SiteHelper.cs | 14 +- .../{BasicContent.cs => FrontMatter.cs} | 7 +- .../{IBaseContent.cs => IFrontMatter.cs} | 2 +- source/Models/{Frontmatter.cs => Page.cs} | 33 ++-- source/Models/Site.cs | 164 +++++++++--------- source/Parser/IFrontmatterParser.cs | 12 +- source/Parser/YAMLParser.cs | 24 +-- source/ServeCommand.cs | 10 +- test/Models/BasicContentTests.cs | 4 +- test/Models/FrontmatterTests.cs | 84 ++++----- test/Models/SiteTests.cs | 2 +- test/Parser/YAMLParserTests.cs | 50 +++--- 15 files changed, 227 insertions(+), 223 deletions(-) rename source/Models/{BasicContent.cs => FrontMatter.cs} (80%) rename source/Models/{IBaseContent.cs => IFrontMatter.cs} (96%) rename source/Models/{Frontmatter.cs => Page.cs} (93%) diff --git a/source/BaseGeneratorCommand.cs b/source/BaseGeneratorCommand.cs index 76c17b0..bc83f34 100644 --- a/source/BaseGeneratorCommand.cs +++ b/source/BaseGeneratorCommand.cs @@ -26,9 +26,9 @@ public abstract class BaseGeneratorCommand protected Site site; /// - /// The frontmatter parser instance. The default is YAML. + /// The front matter parser instance. The default is YAML. /// - protected readonly IFrontmatterParser frontmatterParser = new YAMLParser(); + protected readonly IFrontMatterParser frontMatterParser = new YAMLParser(); /// /// The stopwatch reporter. @@ -57,7 +57,7 @@ public abstract class BaseGeneratorCommand logger.Information("Source path: {source}", propertyValue: options.Source); - site = SiteHelper.Init(configFile, options, frontmatterParser, WhereParamsFilter, logger, stopwatch); + site = SiteHelper.Init(configFile, options, frontMatterParser, WhereParamsFilter, logger, stopwatch); } /// diff --git a/source/BuildCommand.cs b/source/BuildCommand.cs index beffae0..01b21ad 100644 --- a/source/BuildCommand.cs +++ b/source/BuildCommand.cs @@ -47,8 +47,8 @@ public class BuildCommand : BaseGeneratorCommand var pagesCreated = 0; // counter to keep track of the number of pages created _ = Parallel.ForEach(site.PagesReferences, pair => { - var (url, frontmatter) = pair; - var result = frontmatter.CreateOutputFile(); + var (url, page) = pair; + var result = page.CreateOutputFile(); var path = (url + (site.UglyURLs ? "" : "/index.html")).TrimStart('/'); diff --git a/source/Helpers/FileUtils.cs b/source/Helpers/FileUtils.cs index 963b662..ec7c9cd 100644 --- a/source/Helpers/FileUtils.cs +++ b/source/Helpers/FileUtils.cs @@ -12,25 +12,25 @@ namespace SuCoS.Helpers; public static class FileUtils { /// - /// Gets the content of a template file based on the frontmatter and the theme path. + /// Gets the content of a template file based on the page and the theme path. /// /// The theme path. - /// The frontmatter to determine the template index. + /// The page to determine the template index. /// Site data. /// Indicates whether the template is a base template. /// The content of the template file. - public static string GetTemplate(string themePath, Frontmatter frontmatter, Site site, bool isBaseTemplate = false) + public static string GetTemplate(string themePath, Page page, Site site, bool isBaseTemplate = false) { - if (frontmatter is null) + if (page is null) { - throw new ArgumentNullException(nameof(frontmatter)); + throw new ArgumentNullException(nameof(page)); } if (site is null) { throw new ArgumentNullException(nameof(site)); } - var index = (frontmatter.Section, frontmatter.Kind, frontmatter.Type); + var index = (page.Section, page.Kind, page.Type); var cache = isBaseTemplate ? site.baseTemplateCache : site.contentTemplateCache; @@ -38,7 +38,7 @@ public static class FileUtils if (cache.TryGetValue(index, out var content)) return content; - var templatePaths = GetTemplateLookupOrder(themePath, frontmatter, isBaseTemplate); + var templatePaths = GetTemplateLookupOrder(themePath, page, isBaseTemplate); content = GetTemplate(templatePaths); // Cache the template content for future use @@ -72,25 +72,25 @@ public static class FileUtils } /// - /// Gets the lookup order for template files based on the theme path, frontmatter, and template type. + /// Gets the lookup order for template files based on the theme path, page, and template type. /// /// The theme path. - /// The frontmatter to determine the template index. + /// The page to determine the template index. /// Indicates whether the template is a base template. /// The list of template paths in the lookup order. - private static List GetTemplateLookupOrder(string themePath, Frontmatter frontmatter, bool isBaseTemplate) + private static List GetTemplateLookupOrder(string themePath, Page page, bool isBaseTemplate) { - if (frontmatter is null) + if (page is null) { - throw new ArgumentNullException(nameof(frontmatter)); + throw new ArgumentNullException(nameof(page)); } - // 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[] { frontmatter.Section, string.Empty } : new[] { string.Empty }; - var types = new[] { frontmatter.Type, "_default" }; + // Generate the lookup order for template files based on the theme path, page section, type, and kind + var sections = page.Section is not null ? new[] { page.Section, string.Empty } : new[] { string.Empty }; + var types = new[] { page.Type, "_default" }; var kinds = isBaseTemplate - ? new[] { frontmatter.Kind + "-baseof", "baseof" } - : new[] { frontmatter.Kind.ToString() }; + ? new[] { page.Kind + "-baseof", "baseof" } + : new[] { page.Kind.ToString() }; // for each section, each type and each kind return (from section in sections diff --git a/source/Helpers/SiteHelper.cs b/source/Helpers/SiteHelper.cs index 5dcc818..e391105 100644 --- a/source/Helpers/SiteHelper.cs +++ b/source/Helpers/SiteHelper.cs @@ -17,7 +17,7 @@ public static class SiteHelper /// Creates the pages dictionary. /// /// - public static Site Init(string configFile, IGenerateOptions options, IFrontmatterParser frontmatterParser, FilterDelegate whereParamsFilter, ILogger logger, StopwatchReporter stopwatch) + public static Site Init(string configFile, IGenerateOptions options, IFrontMatterParser frontMatterParser, FilterDelegate whereParamsFilter, ILogger logger, StopwatchReporter stopwatch) { if (stopwatch is null) { @@ -27,7 +27,7 @@ public static class SiteHelper Site site; try { - site = ParseSettings(configFile, options, frontmatterParser, whereParamsFilter, logger); + site = ParseSettings(configFile, options, frontMatterParser, whereParamsFilter, logger); } catch { @@ -78,20 +78,20 @@ public static class SiteHelper /// Reads the application settings. /// /// The generate options. - /// The frontmatter parser. + /// The front matter parser. /// The site settings file. /// The method to be used in the whereParams. /// The logger instance. Injectable for testing /// The site settings. - private 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) { throw new ArgumentNullException(nameof(options)); } - if (frontmatterParser is null) + if (frontMatterParser is null) { - throw new ArgumentNullException(nameof(frontmatterParser)); + throw new ArgumentNullException(nameof(frontMatterParser)); } try @@ -104,7 +104,7 @@ public static class SiteHelper } var fileContent = File.ReadAllText(filePath); - var site = frontmatterParser.ParseSiteSettings(fileContent); + var site = frontMatterParser.ParseSiteSettings(fileContent); site.Logger = logger; site.options = options; diff --git a/source/Models/BasicContent.cs b/source/Models/FrontMatter.cs similarity index 80% rename from source/Models/BasicContent.cs rename to source/Models/FrontMatter.cs index f0f6f81..a2454e2 100644 --- a/source/Models/BasicContent.cs +++ b/source/Models/FrontMatter.cs @@ -1,10 +1,12 @@ +using YamlDotNet.Serialization; + namespace SuCoS.Models; /// /// A scafold structure to help creating system-generated content, like /// tag, section or index pages /// -public class BasicContent : IBaseContent +public class FrontMatter : IFrontMatter { /// public string Title { get; } @@ -13,6 +15,7 @@ public class BasicContent : IBaseContent public string Section { get; } /// + [YamlIgnore] public Kind Kind { get; } /// @@ -29,7 +32,7 @@ public class BasicContent : IBaseContent /// /// /// - public BasicContent(string title, string section, string type, string url, Kind kind = Kind.list) + public FrontMatter(string title, string section, string type, string url, Kind kind = Kind.list) { Title = title; Section = section; diff --git a/source/Models/IBaseContent.cs b/source/Models/IFrontMatter.cs similarity index 96% rename from source/Models/IBaseContent.cs rename to source/Models/IFrontMatter.cs index 886cd6e..b452683 100644 --- a/source/Models/IBaseContent.cs +++ b/source/Models/IFrontMatter.cs @@ -3,7 +3,7 @@ namespace SuCoS.Models; /// /// Basic structure needed to generate user content and system content /// -public interface IBaseContent +public interface IFrontMatter { /// /// The content Title. diff --git a/source/Models/Frontmatter.cs b/source/Models/Page.cs similarity index 93% rename from source/Models/Frontmatter.cs rename to source/Models/Page.cs index 6500798..22280ce 100644 --- a/source/Models/Frontmatter.cs +++ b/source/Models/Page.cs @@ -3,7 +3,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using Fluid; using Markdig; using SuCoS.Helpers; @@ -12,9 +11,9 @@ using YamlDotNet.Serialization; namespace SuCoS.Models; /// -/// The meta data about each content Markdown file. +/// Each page data created from source files or from the system. /// -public class Frontmatter : IBaseContent, IParams +public class Page : IFrontMatter, IParams { #region IBaseContent @@ -137,7 +136,7 @@ public class Frontmatter : IBaseContent, IParams /// Used to create the tags list and Related Posts section. /// [YamlIgnore] - public Frontmatter? Parent { get; set; } + public Page? Parent { get; set; } /// /// Plain markdown content, without HTML. @@ -149,7 +148,7 @@ public class Frontmatter : IBaseContent, IParams /// A list of tags, if any. /// [YamlIgnore] - public List? TagsReference { get; set; } + public List? TagsReference { get; set; } /// /// Check if the page is expired @@ -222,13 +221,13 @@ public class Frontmatter : IBaseContent, IParams /// Other content that mention this content. /// Used to create the tags list and Related Posts section. /// - public IEnumerable Pages + public IEnumerable Pages { get { if (PagesReferences is null) { - return new List(); + return new List(); } if (pagesCached is not null) @@ -248,12 +247,12 @@ public class Frontmatter : IBaseContent, IParams /// /// List of pages from the content folder. /// - public IEnumerable RegularPages + public IEnumerable RegularPages { get { regularPagesCache ??= Pages - .Where(frontmatter => frontmatter.Kind == Kind.single) + .Where(page => page.Kind == Kind.single) .ToList(); return regularPagesCache; } @@ -320,9 +319,9 @@ echo page.SourceFileNameWithoutExtension endif -%}"; - private List? regularPagesCache; + private List? regularPagesCache; - private List? pagesCached { get; set; } + private List? pagesCached { get; set; } private DateTime? GetPublishDate => PublishDate ?? Date; @@ -331,7 +330,7 @@ endif /// /// Required. /// - public Frontmatter( + public Page( string title, string sourcePath, Site site, @@ -348,7 +347,7 @@ endif /// /// Constructor /// - public Frontmatter() + public Page() { } @@ -365,8 +364,10 @@ endif /// /// Gets the Permalink path for the file. /// - /// The URL to consider. If null, we get frontmatter.URL + /// The URL to consider. If null use the predefined URL /// The output path. + /// + /// public string CreatePermalink(string? URLforce = null) { var isIndex = SourceFileNameWithoutExtension == "index"; @@ -402,13 +403,13 @@ endif } /// - /// Creates the output file by applying the theme templates to the frontmatter content. + /// Creates the output file by applying the theme templates to the page content. /// /// The processed output file content. public string CreateOutputFile() { // Process the theme base template - // If the theme base template file is available, parse and render the template using the frontmatter data + // If the theme base template file is available, parse and render the template using the page meta data // Otherwise, use the processed content as the final result // Any error during parsing is logged, and an empty string is returned // The final result is stored in the 'result' variable and returned diff --git a/source/Models/Site.cs b/source/Models/Site.cs index e950d02..7924a3f 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -75,7 +75,7 @@ public class Site : IParams /// /// List of all pages, including generated. /// - public List Pages + public List Pages { get { @@ -89,7 +89,7 @@ public class Site : IParams /// /// /// - public Frontmatter? GetPage(string permalink) + public Page? GetPage(string permalink) { return PagesReferences.TryGetValue(permalink, out var page) ? page : null; } @@ -97,12 +97,12 @@ public class Site : IParams /// /// List of all pages, including generated, by their permalink. /// - public Dictionary PagesReferences { get; } = new(); + public Dictionary PagesReferences { get; } = new(); /// /// List of pages from the content folder. /// - public List RegularPages + public List RegularPages { get { @@ -115,9 +115,9 @@ public class Site : IParams } /// - /// The frontmatter of the home page; + /// The page of the home page; /// - public Frontmatter? Home { get; private set; } + public Page? Home { get; private set; } /// /// Command line options @@ -160,9 +160,9 @@ public class Site : IParams public readonly ISystemClock Clock; /// - /// Cache for tag frontmatter. + /// Cache for tag page. /// - private readonly Dictionary automaticContentCache = new(); + private readonly Dictionary automaticContentCache = new(); /// /// The synchronization lock object. @@ -175,13 +175,13 @@ public class Site : IParams private readonly object syncLockPostProcess = new(); /// - /// The frontmatter parser instance. The default is YAML. + /// The front matter parser instance. The default is YAML. /// - private readonly IFrontmatterParser frontmatterParser = new YAMLParser(); + private readonly IFrontMatterParser frontMatterParser = new YAMLParser(); - private List? pagesCache; + private List? pagesCache; - private List? regularPagesCache; + private List? regularPagesCache; /// /// Number of files parsed, used in the report. @@ -210,7 +210,7 @@ public class Site : IParams { // Liquid template options, needed to theme the content // but also parse URLs - TemplateOptions.MemberAccessStrategy.Register(); + TemplateOptions.MemberAccessStrategy.Register(); TemplateOptions.MemberAccessStrategy.Register(); Clock = clock; @@ -236,7 +236,7 @@ public class Site : IParams /// Folder recursive level /// Page of the upper directory /// - public void ParseAndScanSourceFiles(string directory, int level = 0, Frontmatter? pageParent = null) + public void ParseAndScanSourceFiles(string directory, int level = 0, Page? pageParent = null) { directory ??= SourceContentPath; @@ -246,18 +246,18 @@ public class Site : IParams if (indexPath != null) { markdownFiles = markdownFiles.Where(file => file != indexPath).ToArray(); - var frontmatter = ParseSourceFile(pageParent, indexPath); + var page = ParseSourceFile(pageParent, indexPath); if (level == 0) { - Home = frontmatter; - frontmatter!.Permalink = "/"; - frontmatter.Kind = Kind.index; - PagesReferences.Remove(frontmatter.Permalink); - PagesReferences.Add(frontmatter.Permalink, frontmatter); + Home = page; + page!.Permalink = "/"; + page.Kind = Kind.index; + PagesReferences.Remove(page.Permalink); + PagesReferences.Add(page.Permalink, page); } else { - pageParent = frontmatter; + pageParent = page; } } else if (level == 0) @@ -268,13 +268,13 @@ public class Site : IParams else if (level == 1) { var section = new DirectoryInfo(directory).Name; - var contentTemplate = new BasicContent( + var contentTemplate = new FrontMatter( title: section, section: "section", type: "section", url: section ); - pageParent = CreateAutomaticFrontmatter(contentTemplate, null); + pageParent = CreateSystemPage(contentTemplate, null); } _ = Parallel.ForEach(markdownFiles, filePath => @@ -289,17 +289,17 @@ public class Site : IParams } } - private Frontmatter? ParseSourceFile(Frontmatter? pageParent, string filePath) + private Page? ParseSourceFile(Page? parent, string filePath) { - Frontmatter? frontmatter = null; + Page? page = null; try { - frontmatter = frontmatterParser.ParseFrontmatterAndMarkdownFromFile(this, filePath, SourceContentPath) - ?? throw new FormatException($"Error parsing frontmatter for {filePath}"); + page = frontMatterParser.ParseFrontmatterAndMarkdownFromFile(this, filePath, SourceContentPath) + ?? throw new FormatException($"Error parsing front matter for {filePath}"); - if (frontmatter.IsValidDate(options)) + if (page.IsValidDate(options)) { - PostProcessFrontMatter(frontmatter, pageParent, true); + PostProcessPage(page, parent, true); } } catch (Exception ex) @@ -310,71 +310,71 @@ public class Site : IParams // Use interlocked to safely increment the counter in a multi-threaded environment _ = Interlocked.Increment(ref filesParsedToReport); - return frontmatter; + return page; } /// /// Create a page not from the content folder, but as part of the process. /// It's used to create tag pages, section list pages, etc. /// - public Frontmatter CreateAutomaticFrontmatter(BasicContent baseContent, Frontmatter? originalFrontmatter) + public Page CreateSystemPage(FrontMatter frontMatter, Page? originalPage) { - if (baseContent is null) + if (frontMatter is null) { - throw new ArgumentNullException(nameof(baseContent)); + throw new ArgumentNullException(nameof(frontMatter)); } - var id = baseContent.URL; - Frontmatter? frontmatter; + var id = frontMatter.URL; + Page? page; lock (syncLock) { - if (!automaticContentCache.TryGetValue(id, out frontmatter)) + if (!automaticContentCache.TryGetValue(id, out page)) { - frontmatter = new( + page = new( site: this, - title: baseContent.Title, + title: frontMatter.Title, sourcePath: string.Empty, sourceFileNameWithoutExtension: string.Empty, sourcePathDirectory: null ) { - Section = baseContent.Section, - Kind = baseContent.Kind, - Type = baseContent.Type, - URL = baseContent.URL, + Section = frontMatter.Section, + Kind = frontMatter.Kind, + Type = frontMatter.Type, + URL = frontMatter.URL, PagesReferences = new() }; - automaticContentCache.Add(id, frontmatter); - PostProcessFrontMatter(frontmatter); + automaticContentCache.Add(id, page); + PostProcessPage(page); } } - if (frontmatter.Kind != Kind.index && originalFrontmatter?.Permalink is not null) + if (page.Kind != Kind.index && originalPage?.Permalink is not null) { - frontmatter.PagesReferences!.Add(originalFrontmatter.Permalink!); + page.PagesReferences!.Add(originalPage.Permalink!); } // TODO: still too hardcoded - if (frontmatter.Type != "tags" || originalFrontmatter is null) + if (page.Type != "tags" || originalPage is null) { - return frontmatter; + return page; } - lock (originalFrontmatter) + lock (originalPage) { - originalFrontmatter.TagsReference ??= new(); - originalFrontmatter.TagsReference!.Add(frontmatter); + originalPage.TagsReference ??= new(); + originalPage.TagsReference!.Add(page); } - return frontmatter; + return page; } /// - /// Creates the frontmatter for the index page. + /// Creates the page for the site index. /// /// The relative path of the page. - /// The created frontmatter for the index page. - private Frontmatter CreateIndexPage(string relativePath) + /// The created page for the index. + private Page CreateIndexPage(string relativePath) { - Frontmatter frontmatter = new( + Page page = new( title: Title, site: this, sourcePath: Path.Combine(relativePath, "index.md"), @@ -387,74 +387,74 @@ public class Site : IParams URL = "/" }; - PostProcessFrontMatter(frontmatter); - return frontmatter; + PostProcessPage(page); + return page; } /// - /// Extra calculation and automatic data for each frontmatter. + /// Extra calculation and automatic data for each page. /// - /// The given page to be processed + /// The given page to be processed /// The parent page, if any /// - public void PostProcessFrontMatter(Frontmatter frontmatter, Frontmatter? parent = null, bool overwrite = false) + public void PostProcessPage(Page page, Page? parent = null, bool overwrite = false) { - if (frontmatter is null) + if (page is null) { - throw new ArgumentNullException(nameof(frontmatter)); + throw new ArgumentNullException(nameof(page)); } - frontmatter.Parent = parent; - frontmatter.Permalink = frontmatter.CreatePermalink(); + page.Parent = parent; + page.Permalink = page.CreatePermalink(); lock (syncLockPostProcess) { - if (!PagesReferences.TryGetValue(frontmatter.Permalink, out var old) || overwrite) + if (!PagesReferences.TryGetValue(page.Permalink, out var old) || overwrite) { if (old?.PagesReferences is not null) { - frontmatter.PagesReferences ??= new(); - foreach (var page in old.PagesReferences) + page.PagesReferences ??= new(); + foreach (var pageOld in old.PagesReferences) { - frontmatter.PagesReferences.Add(page); + page.PagesReferences.Add(pageOld); } } - if (frontmatter.Aliases is not null) + if (page.Aliases is not null) { - frontmatter.AliasesProcessed ??= new(); - foreach (var alias in frontmatter.Aliases) + page.AliasesProcessed ??= new(); + foreach (var alias in page.Aliases) { - frontmatter.AliasesProcessed.Add(frontmatter.CreatePermalink(alias)); + page.AliasesProcessed.Add(page.CreatePermalink(alias)); } } // Register the page for all urls - foreach (var url in frontmatter.Urls) + foreach (var url in page.Urls) { - PagesReferences[url] = frontmatter; + PagesReferences[url] = page; } } } - if (frontmatter.Tags is not null) + if (page.Tags is not null) { - foreach (var tagName in frontmatter.Tags) + foreach (var tagName in page.Tags) { - var contentTemplate = new BasicContent( + var contentTemplate = new FrontMatter( title: tagName, section: "tags", type: "tags", url: "tags/" + Urlizer.Urlize(tagName) ); - _ = CreateAutomaticFrontmatter(contentTemplate, frontmatter); + _ = CreateSystemPage(contentTemplate, page); } } - if (!string.IsNullOrEmpty(frontmatter.Section) - && PagesReferences.TryGetValue('/' + frontmatter.Section!, out var section)) + if (!string.IsNullOrEmpty(page.Section) + && PagesReferences.TryGetValue('/' + page.Section!, out var section)) { section.PagesReferences ??= new(); - section.PagesReferences.Add(frontmatter.Permalink!); + section.PagesReferences.Add(page.Permalink!); } } } \ No newline at end of file diff --git a/source/Parser/IFrontmatterParser.cs b/source/Parser/IFrontmatterParser.cs index 0277a60..666cc80 100644 --- a/source/Parser/IFrontmatterParser.cs +++ b/source/Parser/IFrontmatterParser.cs @@ -3,27 +3,27 @@ using SuCoS.Models; namespace SuCoS.Parser; /// -/// Responsible for parsing the content frontmatter +/// Responsible for parsing the content front matter /// -public interface IFrontmatterParser +public interface IFrontMatterParser { /// - /// Extract the frontmatter from the content file. + /// Extract the front matter from the content file. /// /// /// /// /// - Frontmatter? ParseFrontmatterAndMarkdownFromFile(Site site, in string filePath, in string sourceContentPath); + Page? ParseFrontmatterAndMarkdownFromFile(Site site, in string filePath, in string sourceContentPath); /// - /// Extract the frontmatter from the content. + /// Extract the front matter from the content. /// /// /// /// /// - Frontmatter? ParseFrontmatterAndMarkdown(Site site, in string filePath, in string fileContent); + Page? ParseFrontmatterAndMarkdown(Site site, in string filePath, in string fileContent); /// /// Parse the app config file. diff --git a/source/Parser/YAMLParser.cs b/source/Parser/YAMLParser.cs index f166971..df344c3 100644 --- a/source/Parser/YAMLParser.cs +++ b/source/Parser/YAMLParser.cs @@ -10,9 +10,9 @@ using YamlDotNet.Serialization; namespace SuCoS.Parser; /// -/// Responsible for parsing the content frontmatter using YAML +/// Responsible for parsing the content front matter using YAML /// -public class YAMLParser : IFrontmatterParser +public class YAMLParser : IFrontMatterParser { /// /// YamlDotNet parser, strictly set to allow automatically parse only known fields @@ -29,7 +29,7 @@ public class YAMLParser : IFrontmatterParser .Build(); /// - public Frontmatter ParseFrontmatterAndMarkdownFromFile(Site site, in string filePath, in string? sourceContentPath = null) + public Page ParseFrontmatterAndMarkdownFromFile(Site site, in string filePath, in string? sourceContentPath = null) { if (site is null) { @@ -56,7 +56,7 @@ public class YAMLParser : IFrontmatterParser } /// - public Frontmatter ParseFrontmatterAndMarkdown(Site site, in string fileRelativePath, in string fileContent) + public Page ParseFrontmatterAndMarkdown(Site site, in string fileRelativePath, in string fileContent) { if (site is null) { @@ -68,28 +68,28 @@ public class YAMLParser : IFrontmatterParser } using var content = new StringReader(fileContent); - var frontmatterBuilder = new StringBuilder(); + var frontMatterBuilder = new StringBuilder(); string? line; while ((line = content.ReadLine()) != null && line != "---") { } while ((line = content.ReadLine()) != null && line != "---") { - frontmatterBuilder.AppendLine(line); + frontMatterBuilder.AppendLine(line); } - // Join the read lines to form the frontmatter - var yaml = frontmatterBuilder.ToString(); + // Join the read lines to form the front matter + var yaml = frontMatterBuilder.ToString(); var rawContent = content.ReadToEnd(); - // Now, you can parse the YAML frontmatter + // Now, you can parse the YAML front matter var page = ParseYAML(ref site, fileRelativePath, yaml, rawContent); return page; } - private Frontmatter ParseYAML(ref Site site, in string filePath, string yaml, in string rawContent) + private Page 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 page = yamlDeserializerRigid.Deserialize(new StringReader(yaml)) ?? throw new FormatException("Error parsing front matter"); var sourceFileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); var section = SiteHelper.GetSection(filePath); page.RawContent = rawContent; @@ -106,7 +106,7 @@ public class YAMLParser : IFrontmatterParser { return page; } - ParseParams(page, typeof(Frontmatter), yaml, yamlDictionary); + ParseParams(page, typeof(Page), yaml, yamlDictionary); return page; } diff --git a/source/ServeCommand.cs b/source/ServeCommand.cs index f34d763..a820486 100644 --- a/source/ServeCommand.cs +++ b/source/ServeCommand.cs @@ -176,7 +176,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable try { - site = SiteHelper.Init(configFile, options, frontmatterParser, WhereParamsFilter, logger, stopwatch); + site = SiteHelper.Init(configFile, options, frontMatterParser, WhereParamsFilter, logger, stopwatch); // Stop the server if (host != null) @@ -231,10 +231,10 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable } // Check if the requested file path corresponds to a registered page - else if (site.PagesReferences.TryGetValue(requestPath, out var frontmatter)) + else if (site.PagesReferences.TryGetValue(requestPath, out var page)) { resultType = "dict"; - await HandleRegisteredPageRequest(context, frontmatter); + await HandleRegisteredPageRequest(context, page); } else @@ -258,9 +258,9 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable await fileStream.CopyToAsync(context.Response.Body); } - private async Task HandleRegisteredPageRequest(HttpContext context, Frontmatter frontmatter) + private async Task HandleRegisteredPageRequest(HttpContext context, Page page) { - var content = frontmatter.CreateOutputFile(); + var content = page.CreateOutputFile(); content = InjectReloadScript(content); await context.Response.WriteAsync(content); } diff --git a/test/Models/BasicContentTests.cs b/test/Models/BasicContentTests.cs index 1241e9e..0416936 100644 --- a/test/Models/BasicContentTests.cs +++ b/test/Models/BasicContentTests.cs @@ -12,7 +12,7 @@ public class BasicContentTests public void Constructor_Sets_Properties_Correctly(string title, string section, string type, string url, Kind kind) { // Act - var basicContent = new BasicContent(title, section, type, url, kind); + var basicContent = new FrontMatter(title, section, type, url, kind); // Assert Assert.Equal(title, basicContent.Title); @@ -29,7 +29,7 @@ public class BasicContentTests const string title = "Title1", section = "Section1", type = "Type1", url = "URL1"; // Act - var basicContent = new BasicContent(title, section, type, url); + var basicContent = new FrontMatter(title, section, type, url); // Assert Assert.Equal(Kind.list, basicContent.Kind); diff --git a/test/Models/FrontmatterTests.cs b/test/Models/FrontmatterTests.cs index 5140a02..fe37c76 100644 --- a/test/Models/FrontmatterTests.cs +++ b/test/Models/FrontmatterTests.cs @@ -26,61 +26,61 @@ public class FrontmatterTests [InlineData("Test Title", "/path/to/file.md", "file", "/path/to")] public void Frontmatter_ShouldCreateWithCorrectProperties(string title, string sourcePath, string sourceFileNameWithoutExtension, string sourcePathDirectory) { - var frontmatter = new Frontmatter(title, sourcePath, site, sourceFileNameWithoutExtension, sourcePathDirectory); + var page = new Page(title, sourcePath, site, sourceFileNameWithoutExtension, sourcePathDirectory); // Assert - Assert.Equal(title, frontmatter.Title); - Assert.Equal(sourcePath, frontmatter.SourcePath); - Assert.Same(site, frontmatter.Site); - Assert.Equal(sourceFileNameWithoutExtension, frontmatter.SourceFileNameWithoutExtension); - Assert.Equal(sourcePathDirectory, frontmatter.SourcePathDirectory); + Assert.Equal(title, page.Title); + Assert.Equal(sourcePath, page.SourcePath); + Assert.Same(site, page.Site); + Assert.Equal(sourceFileNameWithoutExtension, page.SourceFileNameWithoutExtension); + Assert.Equal(sourcePathDirectory, page.SourcePathDirectory); } [Fact] public void Frontmatter_ShouldHaveDefaultValuesForOptionalProperties() { // Arrange - var frontmatter = new Frontmatter("Test Title", "/path/to/file.md", site); + var page = new Page("Test Title", "/path/to/file.md", site); // Assert - Assert.Equal(string.Empty, frontmatter.Section); - Assert.Equal(Kind.single, frontmatter.Kind); - Assert.Equal("page", frontmatter.Type); - Assert.Null(frontmatter.URL); - Assert.Empty(frontmatter.Params); - Assert.Null(frontmatter.Date); - Assert.Null(frontmatter.LastMod); - Assert.Null(frontmatter.PublishDate); - Assert.Null(frontmatter.ExpiryDate); - Assert.Null(frontmatter.AliasesProcessed); - Assert.Null(frontmatter.Permalink); - Assert.Empty(frontmatter.Urls); - Assert.Equal(string.Empty, frontmatter.RawContent); - Assert.Null(frontmatter.TagsReference); - Assert.Null(frontmatter.PagesReferences); - Assert.Empty(frontmatter.RegularPages); - Assert.False(frontmatter.IsDateExpired); - Assert.True(frontmatter.IsDatePublishable); + Assert.Equal(string.Empty, page.Section); + Assert.Equal(Kind.single, page.Kind); + Assert.Equal("page", page.Type); + Assert.Null(page.URL); + Assert.Empty(page.Params); + Assert.Null(page.Date); + Assert.Null(page.LastMod); + Assert.Null(page.PublishDate); + Assert.Null(page.ExpiryDate); + Assert.Null(page.AliasesProcessed); + Assert.Null(page.Permalink); + Assert.Empty(page.Urls); + Assert.Equal(string.Empty, page.RawContent); + Assert.Null(page.TagsReference); + Assert.Null(page.PagesReferences); + Assert.Empty(page.RegularPages); + Assert.False(page.IsDateExpired); + Assert.True(page.IsDatePublishable); } [Fact] public void Aliases_ShouldParseAsUrls() { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) + var page = new Page(titleCONST, sourcePathCONST, site) { Title = "Title", Aliases = new() { "v123", "{{ page.Title }}" } }; // Act - site.PostProcessFrontMatter(frontmatter); + site.PostProcessPage(page); // Assert foreach (var url in new[] { "/v123", "/title" }) { - site.PagesReferences.TryGetValue(url, out var frontmatter1); - Assert.NotNull(frontmatter1); - Assert.Same(frontmatter, frontmatter1); + site.PagesReferences.TryGetValue(url, out var page1); + Assert.NotNull(page1); + Assert.Same(page, page1); } } @@ -89,13 +89,13 @@ public class FrontmatterTests [InlineData(1, false)] public void IsDateExpired_ShouldReturnExpectedResult(int days, bool expected) { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) + var page = new Page(titleCONST, sourcePathCONST, site) { ExpiryDate = clock.Now.AddDays(days) }; // Assert - Assert.Equal(expected, frontmatter.IsDateExpired); + Assert.Equal(expected, page.IsDateExpired); } [Theory] @@ -106,14 +106,14 @@ 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(titleCONST, sourcePathCONST, site) + var page = new Page(titleCONST, sourcePathCONST, site) { PublishDate = publishDate is null ? null : DateTime.Parse(publishDate, CultureInfo.InvariantCulture), Date = date is null ? null : DateTime.Parse(date, CultureInfo.InvariantCulture) }; // Assert - Assert.Equal(expectedValue, frontmatter.IsDatePublishable); + Assert.Equal(expectedValue, page.IsDatePublishable); } [Theory] @@ -121,7 +121,7 @@ public class FrontmatterTests [InlineData(true, true)] public void IsValidDate_ShouldReturnExpectedResult(bool futureOption, bool expected) { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) + var page = new Page(titleCONST, sourcePathCONST, site) { Date = clock.Now.AddDays(1) }; @@ -131,7 +131,7 @@ public class FrontmatterTests options.Setup(o => o.Future).Returns(futureOption); // Assert - Assert.Equal(expected, frontmatter.IsValidDate(options.Object)); + Assert.Equal(expected, page.IsValidDate(options.Object)); } [Theory] @@ -139,13 +139,13 @@ public class FrontmatterTests [InlineData("/another/path", "/test-title")] public void CreatePermalink_ShouldReturnCorrectUrl_WhenUrlIsNull(string sourcePathDirectory, string expectedUrl) { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) + var page = new Page(titleCONST, sourcePathCONST, site) { SourcePathDirectory = sourcePathDirectory }; // Assert - Assert.Equal(expectedUrl, frontmatter.CreatePermalink()); + Assert.Equal(expectedUrl, page.CreatePermalink()); } [Theory] @@ -153,11 +153,11 @@ public class FrontmatterTests [InlineData("{{ page.Title }}/{{ page.SourceFileNameWithoutExtension }}", "/test-title/file")] public void Permalink_CreateWithDefaultOrCustomURLTemplate(string urlTemplate, string expectedPermalink) { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) + var page = new Page(titleCONST, sourcePathCONST, site) { URL = urlTemplate }; - var actualPermalink = frontmatter.CreatePermalink(); + var actualPermalink = page.CreatePermalink(); // Assert Assert.Equal(expectedPermalink, actualPermalink); @@ -168,10 +168,10 @@ public class FrontmatterTests [InlineData(Kind.list, false)] public void RegularPages_ShouldReturnCorrectPages_WhenKindIsSingle(Kind kind, bool isExpectedPage) { - var page = new Frontmatter(titleCONST, sourcePathCONST, site) { Kind = kind }; + var page = new Page(titleCONST, sourcePathCONST, site) { Kind = kind }; // Act - site.PostProcessFrontMatter(page); + site.PostProcessPage(page); // Assert Assert.Equal(isExpectedPage, site.RegularPages.Contains(page)); diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs index 5cb16d1..fb6e717 100644 --- a/test/Models/SiteTests.cs +++ b/test/Models/SiteTests.cs @@ -50,7 +50,7 @@ public class SiteTests var key = (firstKeyPart, secondKeyPart, thirdKeyPart); site.baseTemplateCache.Add(key, value); site.contentTemplateCache.Add(key, value); - site.PagesReferences.Add("test", new Frontmatter("Test Title", "sourcePath", site)); + site.PagesReferences.Add("test", new Page("Test Title", "sourcePath", site)); site.ResetCache(); diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs index ecfefc4..6da78f0 100644 --- a/test/Parser/YAMLParserTests.cs +++ b/test/Parser/YAMLParserTests.cs @@ -82,10 +82,10 @@ Date: 2023-04-01 public void ParseFrontmatter_ShouldParseTitleCorrectly(string fileContent, string expectedTitle) { // Act - var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent); // Asset - Assert.Equal(expectedTitle, frontmatter.Title); + Assert.Equal(expectedTitle, frontMatter.Title); } [Fact] @@ -109,10 +109,10 @@ Date: 2023/01/01 var expectedDate = DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); // Act - var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent); // Asset - Assert.Equal(expectedDate, frontmatter.Date); + Assert.Equal(expectedDate, frontMatter.Date); } [Fact] @@ -124,15 +124,15 @@ Date: 2023/01/01 var expectedExpiryDate = DateTime.Parse("2024-06-01", CultureInfo.InvariantCulture); // Act - var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, 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] @@ -161,10 +161,10 @@ Title [Fact] public void ParseParams_ShouldFillParamsWithNonMatchingFields() { - var page = new Frontmatter("Test Title", "/test.md", siteDefault.Object); + var page = new Page("Test Title", "/test.md", siteDefault.Object); // Act - parser.ParseParams(page, typeof(Frontmatter), pageFrontmaterCONST); + parser.ParseParams(page, typeof(Page), pageFrontmaterCONST); // Asset Assert.True(page.Params.ContainsKey("customParam")); @@ -175,35 +175,35 @@ Title public void ParseFrontmatter_ShouldParseContentInSiteFolder() { var date = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); - var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "", pageContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "", pageContent); // Act - siteDefault.Object.PostProcessFrontMatter(frontmatter); + siteDefault.Object.PostProcessPage(frontMatter); // Asset - Assert.Equal(date, frontmatter.Date); + Assert.Equal(date, frontMatter.Date); } [Fact] public void ParseFrontmatter_ShouldCreateTags() { // Act - var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "", pageContent); - siteDefault.Object.PostProcessFrontMatter(frontmatter); + var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "", pageContent); + siteDefault.Object.PostProcessPage(frontMatter); // Asset - Assert.Equal(2, frontmatter.TagsReference?.Count); + Assert.Equal(2, frontMatter.TagsReference?.Count); } [Fact] public void ParseFrontmatter_ShouldParseCategoriesCorrectly() { - var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.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] @@ -254,9 +254,9 @@ Title [Fact] public void ParseSiteSettings_ShouldReturnContent() { - var frontmatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", pageContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", pageContent); - Assert.Equal(pageMarkdownCONST, frontmatter.RawContent); + Assert.Equal(pageMarkdownCONST, frontMatter.RawContent); } [Fact] -- GitLab From edf598f466fa0f5b6cdea053e6931564c73b4931 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Tue, 11 Jul 2023 14:01:30 -0300 Subject: [PATCH 2/9] refactor: reuse FrontMatter class to generate pages --- source/Models/FrontMatter.cs | 81 +++++++++++++-- source/Models/IFrontMatter.cs | 68 +++++++++++- source/Models/Page.cs | 154 ++++++++++------------------ source/Models/Site.cs | 88 ++++++++++------ source/Parser/IFrontmatterParser.cs | 6 +- source/Parser/YAMLParser.cs | 41 +++----- 6 files changed, 266 insertions(+), 172 deletions(-) diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs index a2454e2..3a19ccf 100644 --- a/source/Models/FrontMatter.cs +++ b/source/Models/FrontMatter.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; +using System.IO; using YamlDotNet.Serialization; namespace SuCoS.Models; @@ -6,23 +9,78 @@ namespace SuCoS.Models; /// A scafold structure to help creating system-generated content, like /// tag, section or index pages /// -public class FrontMatter : IFrontMatter +public class FrontMatter : IFrontMatter, IParams { + #region IFrontMatter + + /// + public string? Title { get; set; } = string.Empty; + + /// + public string? Section { get; set; } = string.Empty; + + /// + public Kind Kind { get; set; } = Kind.single; + + /// + public string? Type { get; set; } = "page"; + + /// + public string? URL { get; init; } + + /// + public string RawContent { get; set; } = string.Empty; + + /// + public DateTime? Date { get; set; } + + /// + public DateTime? LastMod { get; set; } + + /// + public DateTime? PublishDate { get; set; } + + /// + public DateTime? ExpiryDate { get; set; } + + /// + public List? Aliases { get; set; } + /// - public string Title { get; } + public List? Tags { get; set; } /// - public string Section { get; } + public int Weight { get; set; } = 0; /// [YamlIgnore] - public Kind Kind { get; } + public string? SourcePath { get; set; } /// - public string Type { get; } + [YamlIgnore] + public string? SourceFileNameWithoutExtension => Path.GetFileNameWithoutExtension(SourcePath); + + /// + [YamlIgnore] + public string? SourcePathDirectory => Path.GetDirectoryName(SourcePath); /// - public string URL { get; } + [YamlIgnore] + public DateTime? GetPublishDate => PublishDate ?? Date; + + #endregion IFrontMatter + + #region IParams + + /// + public Dictionary Params { get; set; } = new(); + + #endregion IParams + + /// + /// Constructor + /// + public FrontMatter() { } /// /// Constructor @@ -40,4 +98,15 @@ public class FrontMatter : IFrontMatter Type = type; URL = url; } + + /// + /// Constructor + /// + /// + /// + public FrontMatter(string title, string sourcePath) + { + Title = title; + SourcePath = sourcePath; + } } diff --git a/source/Models/IFrontMatter.cs b/source/Models/IFrontMatter.cs index b452683..ac91dbf 100644 --- a/source/Models/IFrontMatter.cs +++ b/source/Models/IFrontMatter.cs @@ -1,9 +1,12 @@ +using System; +using System.Collections.Generic; + namespace SuCoS.Models; /// /// Basic structure needed to generate user content and system content /// -public interface IFrontMatter +public interface IFrontMatter : IParams { /// /// The content Title. @@ -20,18 +23,73 @@ public interface IFrontMatter /// string? Section { get; } + /// + /// The type of content. It's the same of the Section, if not specified. + /// + string? Type { get; } + + /// + /// The URL pattern to be used to create the url. + /// + string? URL { get; } + /// /// The type of the page, if it's a single page, a list of pages or the home page. /// Kind Kind { get; } /// - /// The type of content. It's the same of the Section, if not specified. + /// Gets or sets the date of the page. /// - string? Type { get; } + DateTime? Date { get; } /// - /// The URL pattern to be used to create the url. + /// Gets or sets the last modification date of the page. /// - string? URL { get; } + DateTime? LastMod { get; } + + /// + /// Gets or sets the publish date of the page. + /// + DateTime? PublishDate { get; } + + /// + /// Gets or sets the expiry date of the page. + /// + DateTime? ExpiryDate { get; } + + /// + /// Secondary URL patterns to be used to create the url. + /// + List? Aliases { get; } + + /// + /// A list of tags, if any. + /// + public List? Tags { get; } + + /// + /// Page weight. Useful for sorting. + /// + int Weight { get; } + + /// + /// Raw content, from the Markdown file. + /// + string RawContent { get; } + + /// + /// The source filename, without the extension. ;) + /// + string? SourceFileNameWithoutExtension { get; } + + /// + /// The source directory of the file. + /// + string? SourcePathDirectory { get; } + + /// + /// The date to be considered as the publish date. + /// + DateTime? GetPublishDate => PublishDate ?? Date; } diff --git a/source/Models/Page.cs b/source/Models/Page.cs index 22280ce..4b1af5c 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -13,86 +13,72 @@ namespace SuCoS.Models; /// /// Each page data created from source files or from the system. /// -public class Page : IFrontMatter, IParams +public class Page : IFrontMatter { - #region IBaseContent + readonly FrontMatter frontMatter; - /// - public string? Title { get; set; } = string.Empty; + #region IFrontMatter /// - public string? Section { get; set; } = string.Empty; + public string? Title => frontMatter.Title; /// - public Kind Kind { get; set; } = Kind.single; + public string? Section => frontMatter.Section; /// - public string? Type { get; set; } = "page"; + public Kind Kind + { + get => frontMatter.Kind; + set => frontMatter.Kind = value; + } /// - public string? URL { get; init; } + public string? Type => frontMatter.Type; - #endregion IBaseContent + /// + public string? URL => frontMatter.URL; - #region IParams + /// + public string RawContent => frontMatter.RawContent; /// - [YamlIgnore] - public Dictionary Params { get; set; } = new(); + public string? SourceFileNameWithoutExtension => frontMatter.SourceFileNameWithoutExtension; - #endregion IParams + /// + public string? SourcePathDirectory => frontMatter.SourcePathDirectory; - /// - /// Gets or sets the date of the page. - /// - public DateTime? Date { get; set; } + /// + public string? SourcePath => frontMatter.SourcePath; - /// - /// Gets or sets the last modification date of the page. - /// - public DateTime? LastMod { get; set; } + /// + public Dictionary Params + { + get => frontMatter.Params; + set => frontMatter.Params = value; + } - /// - /// Gets or sets the publish date of the page. - /// - public DateTime? PublishDate { get; set; } + /// + public DateTime? Date => frontMatter.Date; - /// - /// Gets or sets the expiry date of the page. - /// - public DateTime? ExpiryDate { get; set; } + /// + public DateTime? LastMod => frontMatter.LastMod; - /// - /// The path of the file, if it's a file. - /// - public string? SourcePath { get; set; } + /// + public DateTime? PublishDate => frontMatter.PublishDate; - /// - /// Secondary URL patterns to be used to create the url. - /// - public List? Aliases { get; set; } + /// + public DateTime? ExpiryDate => frontMatter.ExpiryDate; - /// - /// Page weight. Useful for sorting. - /// - public int Weight { get; set; } = 0; + /// + public List? Aliases => frontMatter.Aliases; - /// - /// A list of tags, if any. - /// - public List? Tags { get; set; } + /// + public int Weight => frontMatter.Weight; - /// - /// The source filename, without the extension. ;) - /// - [YamlIgnore] - public string? SourceFileNameWithoutExtension { get; set; } + /// + public List? Tags => frontMatter.Tags; - /// - /// The source directory of the file. - /// - [YamlIgnore] - public string? SourcePathDirectory { get; set; } + #endregion IFrontMatter /// /// The source directory of the file. @@ -118,12 +104,6 @@ public class Page : IFrontMatter, IParams [YamlIgnore] public string? Permalink { get; set; } - /// - /// Raw content, from the Markdown file. - /// - [YamlIgnore] - public string RawContent { get; set; } = string.Empty; - /// /// Other content that mention this content. /// Used to create the tags list and Related Posts section. @@ -150,18 +130,6 @@ public class Page : IFrontMatter, IParams [YamlIgnore] public List? TagsReference { get; set; } - /// - /// Check if the page is expired - /// - [YamlIgnore] - public bool IsDateExpired => ExpiryDate is not null && ExpiryDate <= clock.Now; - - /// - /// Check if the page is publishable - /// - [YamlIgnore] - public bool IsDatePublishable => GetPublishDate is null || GetPublishDate <= clock.Now; - /// /// Just a simple check if the current page is the home page /// @@ -178,15 +146,15 @@ public class Page : IFrontMatter, IParams /// Just a simple check if the current page is a "page" /// [YamlIgnore] - public bool IsPage => Type == "page"; + public bool IsPage => Kind == Kind.single; /// /// The number of words in the main content /// [YamlIgnore] - public int WordCount => - Plain.Split(new char[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?' }, - StringSplitOptions.RemoveEmptyEntries).Length; + public int WordCount => Plain.Split(nonWords, StringSplitOptions.RemoveEmptyEntries).Length; + + private static readonly char[] nonWords = new[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r' }; /// /// The markdown content converted to HTML @@ -323,8 +291,6 @@ endif private List? pagesCached { get; set; } - private DateTime? GetPublishDate => PublishDate ?? Date; - private ISystemClock clock => Site.Clock; /// @@ -333,32 +299,22 @@ endif public Page( string title, string sourcePath, - Site site, - string? sourceFileNameWithoutExtension = null, - string? sourcePathDirectory = null) + Site site + ) + : this(new() { - Title = title; - Site = site; - SourcePath = sourcePath; - SourceFileNameWithoutExtension = sourceFileNameWithoutExtension ?? Path.GetFileNameWithoutExtension(sourcePath); - SourcePathDirectory = sourcePathDirectory ?? Path.GetDirectoryName(sourcePath) ?? string.Empty; - } + Title = title, + SourcePath = sourcePath, + }, site) + { } /// /// Constructor /// - public Page() - { - } - - /// - /// Check if the page have a publishing date from the past. - /// - /// options - /// - public bool IsValidDate(IGenerateOptions? options) + public Page(FrontMatter frontMatter, Site site) { - return !IsDateExpired && (IsDatePublishable || (options?.Future ?? false)); + this.frontMatter = frontMatter; + Site = site; } /// diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 7924a3f..1fb8e77 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -70,6 +70,7 @@ public class Site : IParams /// /// The path where the generated site files will be saved. /// + [YamlIgnore] public string OutputPath { get; set; } = "./"; /// @@ -249,10 +250,10 @@ public class Site : IParams var page = ParseSourceFile(pageParent, indexPath); if (level == 0) { + PagesReferences.Remove(page!.Permalink!); Home = page; page!.Permalink = "/"; page.Kind = Kind.index; - PagesReferences.Remove(page.Permalink); PagesReferences.Add(page.Permalink, page); } else @@ -294,11 +295,12 @@ public class Site : IParams Page? page = null; try { - page = frontMatterParser.ParseFrontmatterAndMarkdownFromFile(this, filePath, SourceContentPath) + var frontMatter = frontMatterParser.ParseFrontmatterAndMarkdownFromFile(filePath, SourceContentPath) ?? throw new FormatException($"Error parsing front matter for {filePath}"); - if (page.IsValidDate(options)) + if (IsValidDate(frontMatter, options)) { + page = new(frontMatter, this); PostProcessPage(page, parent, true); } } @@ -319,7 +321,7 @@ public class Site : IParams /// public Page CreateSystemPage(FrontMatter frontMatter, Page? originalPage) { - if (frontMatter is null) + if (frontMatter is null || string.IsNullOrEmpty(frontMatter.URL)) { throw new ArgumentNullException(nameof(frontMatter)); } @@ -328,29 +330,17 @@ public class Site : IParams Page? page; lock (syncLock) { - if (!automaticContentCache.TryGetValue(id, out page)) + if (!automaticContentCache.TryGetValue(key: id, out page)) { - page = new( - site: this, - title: frontMatter.Title, - sourcePath: string.Empty, - sourceFileNameWithoutExtension: string.Empty, - sourcePathDirectory: null - ) - { - Section = frontMatter.Section, - Kind = frontMatter.Kind, - Type = frontMatter.Type, - URL = frontMatter.URL, - PagesReferences = new() - }; + page = new(frontMatter, this); automaticContentCache.Add(id, page); PostProcessPage(page); } } - if (page.Kind != Kind.index && originalPage?.Permalink is not null) + if (page.Kind != Kind.index && !string.IsNullOrEmpty(originalPage?.Permalink)) { + page.PagesReferences ??= new(); page.PagesReferences!.Add(originalPage.Permalink!); } @@ -374,18 +364,15 @@ public class Site : IParams /// The created page for the index. private Page CreateIndexPage(string relativePath) { - Page page = new( - title: Title, - site: this, - sourcePath: Path.Combine(relativePath, "index.md"), - sourceFileNameWithoutExtension: "index", - sourcePathDirectory: "/" - ) + Page page = new(new() { + Title = Title, + SourcePath = Path.Combine(relativePath, "index.md"), Kind = string.IsNullOrEmpty(relativePath) ? Kind.index : Kind.list, Section = (string.IsNullOrEmpty(relativePath) ? Kind.index : Kind.list).ToString(), - URL = "/" - }; + URL = "/", + Type = "index", + }, this); PostProcessPage(page); return page; @@ -440,13 +427,13 @@ public class Site : IParams { foreach (var tagName in page.Tags) { - var contentTemplate = new FrontMatter( + FrontMatter contentTemplate = new( title: tagName, section: "tags", type: "tags", url: "tags/" + Urlizer.Urlize(tagName) ); - _ = CreateSystemPage(contentTemplate, page); + CreateSystemPage(contentTemplate, page); } } @@ -457,4 +444,43 @@ public class Site : IParams section.PagesReferences.Add(page.Permalink!); } } + + /// + /// Check if the page have a publishing date from the past. + /// + /// Page or front matter + /// options + /// + public bool IsValidDate(IFrontMatter frontMatter, IGenerateOptions? options) + { + if (frontMatter is null) + { + throw new ArgumentNullException(nameof(frontMatter)); + } + return !IsDateExpired(frontMatter) && (IsDatePublishable(frontMatter) || (options?.Future ?? false)); + } + + /// + /// Check if the page is expired + /// + public bool IsDateExpired(IFrontMatter frontMatter) + { + if (frontMatter is null) + { + throw new ArgumentNullException(nameof(frontMatter)); + } + return frontMatter.ExpiryDate is not null && frontMatter.ExpiryDate <= Clock.Now; + } + + /// + /// Check if the page is publishable + /// + public bool IsDatePublishable(IFrontMatter frontMatter) + { + if (frontMatter is null) + { + throw new ArgumentNullException(nameof(frontMatter)); + } + return frontMatter.GetPublishDate is null || frontMatter.GetPublishDate <= Clock.Now; + } } \ No newline at end of file diff --git a/source/Parser/IFrontmatterParser.cs b/source/Parser/IFrontmatterParser.cs index 666cc80..b2d967c 100644 --- a/source/Parser/IFrontmatterParser.cs +++ b/source/Parser/IFrontmatterParser.cs @@ -10,20 +10,18 @@ public interface IFrontMatterParser /// /// Extract the front matter from the content file. /// - /// /// /// /// - Page? ParseFrontmatterAndMarkdownFromFile(Site site, in string filePath, in string sourceContentPath); + FrontMatter? ParseFrontmatterAndMarkdownFromFile(in string filePath, in string sourceContentPath); /// /// Extract the front matter from the content. /// - /// /// /// /// - Page? ParseFrontmatterAndMarkdown(Site site, in string filePath, in string fileContent); + FrontMatter? ParseFrontmatterAndMarkdown(in string filePath, in string fileContent); /// /// Parse the app config file. diff --git a/source/Parser/YAMLParser.cs b/source/Parser/YAMLParser.cs index df344c3..ddc9380 100644 --- a/source/Parser/YAMLParser.cs +++ b/source/Parser/YAMLParser.cs @@ -29,12 +29,8 @@ public class YAMLParser : IFrontMatterParser .Build(); /// - public Page ParseFrontmatterAndMarkdownFromFile(Site site, in string filePath, in string? sourceContentPath = null) + public FrontMatter ParseFrontmatterAndMarkdownFromFile( in string filePath, in string? sourceContentPath = null) { - if (site is null) - { - throw new ArgumentNullException(nameof(site)); - } if (filePath is null) { throw new ArgumentNullException(nameof(filePath)); @@ -52,16 +48,12 @@ public class YAMLParser : IFrontMatterParser throw new FileNotFoundException(filePath, ex); } - return ParseFrontmatterAndMarkdown(site, fileRelativePath, fileContent); + return ParseFrontmatterAndMarkdown(fileRelativePath, fileContent); } /// - public Page ParseFrontmatterAndMarkdown(Site site, in string fileRelativePath, in string fileContent) + public FrontMatter ParseFrontmatterAndMarkdown(in string fileRelativePath, in string fileContent) { - if (site is null) - { - throw new ArgumentNullException(nameof(site)); - } if (fileRelativePath is null) { throw new ArgumentNullException(nameof(fileRelativePath)); @@ -82,33 +74,28 @@ public class YAMLParser : IFrontMatterParser var rawContent = content.ReadToEnd(); // Now, you can parse the YAML front matter - var page = ParseYAML(ref site, fileRelativePath, yaml, rawContent); + var page = ParseYAML(fileRelativePath, yaml, rawContent); return page; } - private Page ParseYAML(ref Site site, in string filePath, string yaml, in string rawContent) + private FrontMatter ParseYAML(in string filePath, string yaml, in string rawContent) { - var page = yamlDeserializerRigid.Deserialize(new StringReader(yaml)) ?? throw new FormatException("Error parsing front matter"); - var sourceFileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); + var frontMatter = yamlDeserializerRigid.Deserialize(new StringReader(yaml)) ?? throw new FormatException("Error parsing front matter"); var section = SiteHelper.GetSection(filePath); - page.RawContent = rawContent; - page.Section = section; - page.Site = site; - page.SourceFileNameWithoutExtension = sourceFileNameWithoutExtension; - page.SourcePath = filePath; - page.SourcePathDirectory = Path.GetDirectoryName(filePath); - page.Title ??= sourceFileNameWithoutExtension; - page.Type ??= section; + frontMatter.RawContent = rawContent; + frontMatter.Section = section; + frontMatter.SourcePath = filePath; + frontMatter.Type ??= section; var yamlObject = yamlDeserializer.Deserialize(new StringReader(yaml)); - if (yamlObject is not Dictionary yamlDictionary || !(yamlDictionary.TryGetValue("Tags", out var tags) && tags is List tagsValues)) + if (yamlObject is not Dictionary yamlDictionary) { - return page; + return frontMatter; } - ParseParams(page, typeof(Page), yaml, yamlDictionary); + ParseParams(frontMatter, typeof(Page), yaml, yamlDictionary); - return page; + return frontMatter; } /// -- GitLab From 0ecab9a83bba432b3882f042d9192b7b8c9b8a97 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Tue, 11 Jul 2023 14:02:42 -0300 Subject: [PATCH 3/9] tests: 3 new test sites to predict scenarios --- test/.TestSites/01/content/categories.md | 6 + .../01/content/{test03.md => date-future.md} | 5 +- test/.TestSites/01/content/date-ok.md | 8 ++ test/.TestSites/01/content/expired.md | 8 ++ .../01/content/publishdate-future.md | 8 ++ test/.TestSites/01/content/publishdate-ok.md | 8 ++ test/.TestSites/01/content/test02.md | 5 - .../.TestSites/02-have-index/content/alias.md | 8 ++ .../02-have-index/content/categories.md | 6 + .../02-have-index/content/date-future.md | 8 ++ .../02-have-index/content/date-ok.md | 8 ++ .../02-have-index/content/expired.md | 8 ++ .../.TestSites/02-have-index/content/index.md | 5 + .../content/publishdate-future.md | 8 ++ .../02-have-index/content/publishdate-ok.md | 8 ++ .../02-have-index/content/test01.md | 5 + .../03-section/content/blog/alias.md | 8 ++ .../03-section/content/blog/categories.md | 6 + .../03-section/content/blog/date-future.md | 8 ++ .../03-section/content/blog/date-ok.md | 8 ++ .../03-section/content/blog/expired.md | 8 ++ .../content/blog/publishdate-future.md | 8 ++ .../03-section/content/blog/publishdate-ok.md | 8 ++ .../03-section/content/blog/test01.md | 5 + test/Models/BasicContentTests.cs | 37 ------ test/Models/FrontMatterTests.cs | 90 +++++++++++++ .../{FrontmatterTests.cs => PageTests.cs} | 121 +++++++++++++----- test/Models/SiteTests.cs | 68 +++++++++- test/Parser/YAMLParserTests.cs | 44 +++---- 29 files changed, 424 insertions(+), 107 deletions(-) create mode 100644 test/.TestSites/01/content/categories.md rename test/.TestSites/01/content/{test03.md => date-future.md} (50%) create mode 100644 test/.TestSites/01/content/date-ok.md create mode 100644 test/.TestSites/01/content/expired.md create mode 100644 test/.TestSites/01/content/publishdate-future.md create mode 100644 test/.TestSites/01/content/publishdate-ok.md delete mode 100644 test/.TestSites/01/content/test02.md create mode 100644 test/.TestSites/02-have-index/content/alias.md create mode 100644 test/.TestSites/02-have-index/content/categories.md create mode 100644 test/.TestSites/02-have-index/content/date-future.md create mode 100644 test/.TestSites/02-have-index/content/date-ok.md create mode 100644 test/.TestSites/02-have-index/content/expired.md create mode 100644 test/.TestSites/02-have-index/content/index.md create mode 100644 test/.TestSites/02-have-index/content/publishdate-future.md create mode 100644 test/.TestSites/02-have-index/content/publishdate-ok.md create mode 100644 test/.TestSites/02-have-index/content/test01.md create mode 100644 test/.TestSites/03-section/content/blog/alias.md create mode 100644 test/.TestSites/03-section/content/blog/categories.md create mode 100644 test/.TestSites/03-section/content/blog/date-future.md create mode 100644 test/.TestSites/03-section/content/blog/date-ok.md create mode 100644 test/.TestSites/03-section/content/blog/expired.md create mode 100644 test/.TestSites/03-section/content/blog/publishdate-future.md create mode 100644 test/.TestSites/03-section/content/blog/publishdate-ok.md create mode 100644 test/.TestSites/03-section/content/blog/test01.md delete mode 100644 test/Models/BasicContentTests.cs create mode 100644 test/Models/FrontMatterTests.cs rename test/Models/{FrontmatterTests.cs => PageTests.cs} (61%) diff --git a/test/.TestSites/01/content/categories.md b/test/.TestSites/01/content/categories.md new file mode 100644 index 0000000..4065d67 --- /dev/null +++ b/test/.TestSites/01/content/categories.md @@ -0,0 +1,6 @@ +--- +Title: Categories +Categories: ['Test', 'Real Data'] +--- + +Categories diff --git a/test/.TestSites/01/content/test03.md b/test/.TestSites/01/content/date-future.md similarity index 50% rename from test/.TestSites/01/content/test03.md rename to test/.TestSites/01/content/date-future.md index c1f3586..5a6a90e 100644 --- a/test/.TestSites/01/content/test03.md +++ b/test/.TestSites/01/content/date-future.md @@ -1,9 +1,8 @@ --- -Title: Real Data Title +Title: Date Future Date: 2023-07-01 -Categories: ['Test', 'Real Data'] --- -# Real Data Test +## Real Data Test This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/01/content/date-ok.md b/test/.TestSites/01/content/date-ok.md new file mode 100644 index 0000000..d4363e7 --- /dev/null +++ b/test/.TestSites/01/content/date-ok.md @@ -0,0 +1,8 @@ +--- +Title: Date-OK +Date: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/01/content/expired.md b/test/.TestSites/01/content/expired.md new file mode 100644 index 0000000..02ae1bb --- /dev/null +++ b/test/.TestSites/01/content/expired.md @@ -0,0 +1,8 @@ +--- +Title: Expired +ExpiryDate: 2020-04-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/01/content/publishdate-future.md b/test/.TestSites/01/content/publishdate-future.md new file mode 100644 index 0000000..70e8da6 --- /dev/null +++ b/test/.TestSites/01/content/publishdate-future.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate Future +PublishDate: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/01/content/publishdate-ok.md b/test/.TestSites/01/content/publishdate-ok.md new file mode 100644 index 0000000..f4507ff --- /dev/null +++ b/test/.TestSites/01/content/publishdate-ok.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate OK +PublishDate: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/01/content/test02.md b/test/.TestSites/01/content/test02.md deleted file mode 100644 index 7dfb40d..0000000 --- a/test/.TestSites/01/content/test02.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -Title: Test Content 2 ---- - -Test Content 2 diff --git a/test/.TestSites/02-have-index/content/alias.md b/test/.TestSites/02-have-index/content/alias.md new file mode 100644 index 0000000..44e58b4 --- /dev/null +++ b/test/.TestSites/02-have-index/content/alias.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Aliases: + - v123 + - "{{ page.Title }}-2" +--- + +Test Alias diff --git a/test/.TestSites/02-have-index/content/categories.md b/test/.TestSites/02-have-index/content/categories.md new file mode 100644 index 0000000..4065d67 --- /dev/null +++ b/test/.TestSites/02-have-index/content/categories.md @@ -0,0 +1,6 @@ +--- +Title: Categories +Categories: ['Test', 'Real Data'] +--- + +Categories diff --git a/test/.TestSites/02-have-index/content/date-future.md b/test/.TestSites/02-have-index/content/date-future.md new file mode 100644 index 0000000..5a6a90e --- /dev/null +++ b/test/.TestSites/02-have-index/content/date-future.md @@ -0,0 +1,8 @@ +--- +Title: Date Future +Date: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/02-have-index/content/date-ok.md b/test/.TestSites/02-have-index/content/date-ok.md new file mode 100644 index 0000000..d4363e7 --- /dev/null +++ b/test/.TestSites/02-have-index/content/date-ok.md @@ -0,0 +1,8 @@ +--- +Title: Date-OK +Date: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/02-have-index/content/expired.md b/test/.TestSites/02-have-index/content/expired.md new file mode 100644 index 0000000..02ae1bb --- /dev/null +++ b/test/.TestSites/02-have-index/content/expired.md @@ -0,0 +1,8 @@ +--- +Title: Expired +ExpiryDate: 2020-04-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/02-have-index/content/index.md b/test/.TestSites/02-have-index/content/index.md new file mode 100644 index 0000000..28806b1 --- /dev/null +++ b/test/.TestSites/02-have-index/content/index.md @@ -0,0 +1,5 @@ +--- +Title: My Home Page +--- + +Index Content diff --git a/test/.TestSites/02-have-index/content/publishdate-future.md b/test/.TestSites/02-have-index/content/publishdate-future.md new file mode 100644 index 0000000..70e8da6 --- /dev/null +++ b/test/.TestSites/02-have-index/content/publishdate-future.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate Future +PublishDate: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/02-have-index/content/publishdate-ok.md b/test/.TestSites/02-have-index/content/publishdate-ok.md new file mode 100644 index 0000000..f4507ff --- /dev/null +++ b/test/.TestSites/02-have-index/content/publishdate-ok.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate OK +PublishDate: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/02-have-index/content/test01.md b/test/.TestSites/02-have-index/content/test01.md new file mode 100644 index 0000000..2d3c415 --- /dev/null +++ b/test/.TestSites/02-have-index/content/test01.md @@ -0,0 +1,5 @@ +--- +Title: Test Content 1 +--- + +Test Content 1 diff --git a/test/.TestSites/03-section/content/blog/alias.md b/test/.TestSites/03-section/content/blog/alias.md new file mode 100644 index 0000000..44e58b4 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/alias.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Aliases: + - v123 + - "{{ page.Title }}-2" +--- + +Test Alias diff --git a/test/.TestSites/03-section/content/blog/categories.md b/test/.TestSites/03-section/content/blog/categories.md new file mode 100644 index 0000000..4065d67 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/categories.md @@ -0,0 +1,6 @@ +--- +Title: Categories +Categories: ['Test', 'Real Data'] +--- + +Categories diff --git a/test/.TestSites/03-section/content/blog/date-future.md b/test/.TestSites/03-section/content/blog/date-future.md new file mode 100644 index 0000000..5a6a90e --- /dev/null +++ b/test/.TestSites/03-section/content/blog/date-future.md @@ -0,0 +1,8 @@ +--- +Title: Date Future +Date: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/03-section/content/blog/date-ok.md b/test/.TestSites/03-section/content/blog/date-ok.md new file mode 100644 index 0000000..d4363e7 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/date-ok.md @@ -0,0 +1,8 @@ +--- +Title: Date-OK +Date: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/03-section/content/blog/expired.md b/test/.TestSites/03-section/content/blog/expired.md new file mode 100644 index 0000000..02ae1bb --- /dev/null +++ b/test/.TestSites/03-section/content/blog/expired.md @@ -0,0 +1,8 @@ +--- +Title: Expired +ExpiryDate: 2020-04-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/03-section/content/blog/publishdate-future.md b/test/.TestSites/03-section/content/blog/publishdate-future.md new file mode 100644 index 0000000..70e8da6 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/publishdate-future.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate Future +PublishDate: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/03-section/content/blog/publishdate-ok.md b/test/.TestSites/03-section/content/blog/publishdate-ok.md new file mode 100644 index 0000000..f4507ff --- /dev/null +++ b/test/.TestSites/03-section/content/blog/publishdate-ok.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate OK +PublishDate: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/03-section/content/blog/test01.md b/test/.TestSites/03-section/content/blog/test01.md new file mode 100644 index 0000000..2d3c415 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/test01.md @@ -0,0 +1,5 @@ +--- +Title: Test Content 1 +--- + +Test Content 1 diff --git a/test/Models/BasicContentTests.cs b/test/Models/BasicContentTests.cs deleted file mode 100644 index 0416936..0000000 --- a/test/Models/BasicContentTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using SuCoS.Models; -using Xunit; - -namespace Test.Models; - -public class BasicContentTests -{ - [Theory] - [InlineData("Title1", "Section1", "Type1", "URL1", Kind.single)] - [InlineData("Title2", "Section2", "Type2", "URL2", Kind.list)] - [InlineData("Title3", "Section3", "Type3", "URL3", Kind.index)] - public void Constructor_Sets_Properties_Correctly(string title, string section, string type, string url, Kind kind) - { - // Act - var basicContent = new FrontMatter(title, section, type, url, kind); - - // Assert - Assert.Equal(title, basicContent.Title); - Assert.Equal(section, basicContent.Section); - Assert.Equal(type, basicContent.Type); - Assert.Equal(url, basicContent.URL); - Assert.Equal(kind, basicContent.Kind); - } - - [Fact] - public void Constructor_Sets_Kind_To_List_If_Not_Provided() - { - // Arrange - const string title = "Title1", section = "Section1", type = "Type1", url = "URL1"; - - // Act - var basicContent = new FrontMatter(title, section, type, url); - - // Assert - Assert.Equal(Kind.list, basicContent.Kind); - } -} diff --git a/test/Models/FrontMatterTests.cs b/test/Models/FrontMatterTests.cs new file mode 100644 index 0000000..5930e02 --- /dev/null +++ b/test/Models/FrontMatterTests.cs @@ -0,0 +1,90 @@ +using System.Globalization; +using SuCoS.Models; +using Xunit; + +namespace Test.Models; + +public class FrontMatterTests +{ + [Theory] + [InlineData("Title1", "Section1", "Type1", "URL1", Kind.single)] + [InlineData("Title2", "Section2", "Type2", "URL2", Kind.list)] + [InlineData("Title3", "Section3", "Type3", "URL3", Kind.index)] + public void Constructor_Sets_Properties_Correctly(string title, string section, string type, string url, Kind kind) + { + // Act + var basicContent = new FrontMatter(title, section, type, url, kind); + + // Assert + Assert.Equal(title, basicContent.Title); + Assert.Equal(section, basicContent.Section); + Assert.Equal(type, basicContent.Type); + Assert.Equal(url, basicContent.URL); + Assert.Equal(kind, basicContent.Kind); + } + + [Fact] + public void Constructor_Sets_Kind_To_List_If_Not_Provided() + { + // Arrange + const string title = "Title1", section = "Section1", type = "Type1", url = "URL1"; + + // Act + var basicContent = new FrontMatter(title, section, type, url); + + // Assert + Assert.Equal(Kind.list, basicContent.Kind); + } + + [Theory] + [InlineData("C:/Test/Document.txt", "Document")] + [InlineData("C:/Test/SubFolder/Document.txt", "Document")] + [InlineData("Document.txt", "Document")] + public void SourceFileNameWithoutExtension_Returns_Correct_FileName(string sourcePath, string expectedFileName) + { + // Arrange + var frontMatter = new FrontMatter("Title", sourcePath); + + // Act + var actualFileName = frontMatter.SourceFileNameWithoutExtension; + + // Assert + Assert.Equal(expectedFileName, actualFileName); + } + + [Theory] + [InlineData("C:/Test/Document.txt", "C:/Test")] + [InlineData("C:/Test/SubFolder/Document.txt", "C:/Test/SubFolder")] + [InlineData("/home/Test/Document.txt", "/home/Test")] + [InlineData("/Test/SubFolder/Document.txt", "/Test/SubFolder")] + [InlineData("Document.txt", "")] + public void SourcePathDirectory_Returns_Correct_Directory(string sourcePath, string expectedDirectory) + { + // Arrange + var frontMatter = new FrontMatter("Title", sourcePath); + + // Assert + Assert.Equal(expectedDirectory, frontMatter.SourcePathDirectory); + } + + [Theory] + [InlineData("2023-07-11T00:00:00", "2023-07-12T00:00:00", "2023-07-12T00:00:00")] + [InlineData("2023-07-11T00:00:00", null, "2023-07-11T00:00:00")] + [InlineData("2023-07-11", "2023-07-12", "2023-07-12")] + [InlineData("2023-07-11", null, "2023-07-11")] + [InlineData(null, null, null)] + public void GetPublishDate_Returns_PublishDate_If_Not_Null_Otherwise_Date(string dateString, string publishDateString, string expectedDateString) + { + // Arrange + var date = dateString == null ? (DateTime?)null : DateTime.Parse(dateString, CultureInfo.InvariantCulture); + var publishDate = publishDateString == null ? (DateTime?)null : DateTime.Parse(publishDateString, CultureInfo.InvariantCulture); + var expectedDate = expectedDateString == null ? (DateTime?)null : DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); + + // Act + var frontMatter = new FrontMatter() { Date = date, PublishDate = publishDate }; + + // Assert + Assert.Equal(expectedDate, frontMatter.GetPublishDate); + Assert.Equal(expectedDate, (frontMatter as IFrontMatter).GetPublishDate); + } +} diff --git a/test/Models/FrontmatterTests.cs b/test/Models/PageTests.cs similarity index 61% rename from test/Models/FrontmatterTests.cs rename to test/Models/PageTests.cs index fe37c76..3fc2a02 100644 --- a/test/Models/FrontmatterTests.cs +++ b/test/Models/PageTests.cs @@ -6,15 +6,37 @@ using Xunit; namespace Test.Models; -public class FrontmatterTests +public class PageTests { private readonly ISystemClock clock; 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() + private const string markdown1CONST = @" +# word01 word02 + +word03 word04 word05 6 7 eight + +## nine + +```cs +console.WriteLine('hello word') +```"; + private const string markdown2CONST = @" +# word01 word02 + +word03 word04 word05 6 7 [eight](http://example.com)"; + private const string markdownPlain1CONST = @"word01 word02 +word03 word04 word05 6 7 eight +nine +console.WriteLine('hello word') +"; + private const string markdownPlain2CONST = @"word01 word02 +word03 word04 word05 6 7 eight +"; + + public PageTests() { var testDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); systemClockMock.Setup(c => c.Now).Returns(testDate); @@ -26,7 +48,7 @@ public class FrontmatterTests [InlineData("Test Title", "/path/to/file.md", "file", "/path/to")] public void Frontmatter_ShouldCreateWithCorrectProperties(string title, string sourcePath, string sourceFileNameWithoutExtension, string sourcePathDirectory) { - var page = new Page(title, sourcePath, site, sourceFileNameWithoutExtension, sourcePathDirectory); + var page = new Page("Test Title", "/path/to/file.md", site); // Assert Assert.Equal(title, page.Title); @@ -59,29 +81,30 @@ public class FrontmatterTests Assert.Null(page.TagsReference); Assert.Null(page.PagesReferences); Assert.Empty(page.RegularPages); - Assert.False(page.IsDateExpired); - Assert.True(page.IsDatePublishable); + Assert.False(site.IsDateExpired(page)); + Assert.True(site.IsDatePublishable(page)); } - [Fact] - public void Aliases_ShouldParseAsUrls() + [Theory] + [InlineData("/v123")] + [InlineData("/test-title-2")] + public void Aliases_ShouldParseAsUrls(string url) { - var page = new Page(titleCONST, sourcePathCONST, site) + var page = new Page(new() { - Title = "Title", - Aliases = new() { "v123", "{{ page.Title }}" } - }; + Title = titleCONST, + SourcePath = sourcePathCONST, + Aliases = new() { "v123", "{{ page.Title }}", "{{ page.Title }}-2" } + }, site); // Act site.PostProcessPage(page); // Assert - foreach (var url in new[] { "/v123", "/title" }) - { - site.PagesReferences.TryGetValue(url, out var page1); - Assert.NotNull(page1); - Assert.Same(page, page1); - } + Assert.Equal(3, site.PagesReferences.Count); + site.PagesReferences.TryGetValue(url, out var pageOther); + Assert.NotNull(pageOther); + Assert.Same(page, pageOther); } [Theory] @@ -89,13 +112,13 @@ public class FrontmatterTests [InlineData(1, false)] public void IsDateExpired_ShouldReturnExpectedResult(int days, bool expected) { - var page = new Page(titleCONST, sourcePathCONST, site) + var page = new Page(new(titleCONST, sourcePathCONST) { ExpiryDate = clock.Now.AddDays(days) - }; + }, site); // Assert - Assert.Equal(expected, page.IsDateExpired); + Assert.Equal(expected, site.IsDateExpired(page)); } [Theory] @@ -106,14 +129,14 @@ public class FrontmatterTests [InlineData("2022-06-28", "2024-06-28", true)] public void IsDatePublishable_ShouldReturnCorrectValues(string? publishDate, string? date, bool expectedValue) { - var page = new Page(titleCONST, sourcePathCONST, site) + var page = new Page(new(titleCONST, sourcePathCONST) { PublishDate = publishDate is null ? null : DateTime.Parse(publishDate, CultureInfo.InvariantCulture), Date = date is null ? null : DateTime.Parse(date, CultureInfo.InvariantCulture) - }; + }, site); // Assert - Assert.Equal(expectedValue, page.IsDatePublishable); + Assert.Equal(expectedValue, site.IsDatePublishable(page)); } [Theory] @@ -121,28 +144,28 @@ public class FrontmatterTests [InlineData(true, true)] public void IsValidDate_ShouldReturnExpectedResult(bool futureOption, bool expected) { - var page = new Page(titleCONST, sourcePathCONST, site) + var page = new Page(new(titleCONST, sourcePathCONST) { Date = clock.Now.AddDays(1) - }; + }, site); // Act var options = new Mock(); options.Setup(o => o.Future).Returns(futureOption); // Assert - Assert.Equal(expected, page.IsValidDate(options.Object)); + Assert.Equal(expected, site.IsValidDate(page, options.Object)); } [Theory] - [InlineData("/test/path", "/test-title")] - [InlineData("/another/path", "/test-title")] - public void CreatePermalink_ShouldReturnCorrectUrl_WhenUrlIsNull(string sourcePathDirectory, string expectedUrl) + [InlineData("/test/path/index.md", "/test-title")] + [InlineData("/another/path/index.md", "/test-title")] + public void CreatePermalink_ShouldReturnCorrectUrl_WhenUrlIsNull(string sourcePath, string expectedUrl) { - var page = new Page(titleCONST, sourcePathCONST, site) + var page = new Page(new(titleCONST, sourcePathCONST) { - SourcePathDirectory = sourcePathDirectory - }; + SourcePath = sourcePath + }, site); // Assert Assert.Equal(expectedUrl, page.CreatePermalink()); @@ -153,10 +176,10 @@ public class FrontmatterTests [InlineData("{{ page.Title }}/{{ page.SourceFileNameWithoutExtension }}", "/test-title/file")] public void Permalink_CreateWithDefaultOrCustomURLTemplate(string urlTemplate, string expectedPermalink) { - var page = new Page(titleCONST, sourcePathCONST, site) + var page = new Page(new(titleCONST, sourcePathCONST) { URL = urlTemplate - }; + }, site); var actualPermalink = page.CreatePermalink(); // Assert @@ -176,4 +199,32 @@ public class FrontmatterTests // Assert Assert.Equal(isExpectedPage, site.RegularPages.Contains(page)); } + + [Theory] + [InlineData(markdown1CONST, 13)] + [InlineData(markdown2CONST, 8)] + public void WordCount_ShouldReturnCorrectCounts(string rawContent, int wordCountExpected) + { + var page = new Page(new() + { + RawContent = rawContent + }, site); + + // Assert + Assert.Equal(wordCountExpected, page.WordCount); + } + + [Theory] + [InlineData(markdown1CONST, markdownPlain1CONST)] + [InlineData(markdown2CONST, markdownPlain2CONST)] + public void Plain_ShouldReturnCorrectPlainString(string rawContent, string plain) + { + var page = new Page(new() + { + RawContent = rawContent + }, site); + + // Assert + Assert.Equal(plain, page.Plain); + } } diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs index fb6e717..977f396 100644 --- a/test/Models/SiteTests.cs +++ b/test/Models/SiteTests.cs @@ -1,7 +1,6 @@ using Xunit; using Moq; using System.Globalization; -using SuCoS.Helpers; using SuCoS.Models; namespace Test.Models; @@ -14,6 +13,8 @@ public class SiteTests private readonly Site site; private readonly Mock systemClockMock = new(); private const string testSite1PathCONST = ".TestSites/01"; + private const string testSite2PathCONST = ".TestSites/02-have-index"; + private const string testSite3PathCONST = ".TestSites/03-section"; // based on the compiled test.dll path // that is typically "bin/Debug/netX.0/test.dll" @@ -28,20 +29,81 @@ public class SiteTests [Theory] [InlineData("test01.md")] - [InlineData("test02.md")] + [InlineData("date-ok.md")] public void Test_ScanAllMarkdownFiles(string fileName) { var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSite1PathCONST)); // Act - site.ParseAndScanSourceFiles(siteFullPath); + site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); // Assert Assert.Contains(site.Pages, page => page.SourcePathDirectory?.Length == 0); Assert.Contains(site.Pages, page => page.SourceFileNameWithoutExtension == fileNameWithoutExtension); } + [Theory] + [InlineData(testSite1PathCONST)] + [InlineData(testSite2PathCONST)] + public void Home_ShouldReturnAHomePage(string sitePath) + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); + + // Act + site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); + + // Assert + Assert.NotNull(site.Home); + Assert.True(site.Home.IsHome); + Assert.Single(site.PagesReferences.Values.Where(page => page.IsHome)); + } + + [Theory] + [InlineData(testSite1PathCONST, 0)] + [InlineData(testSite2PathCONST, 0)] + [InlineData(testSite3PathCONST, 1)] + public void Home_ShouldReturnIsSection(string sitePath, int expectedQuantity) + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); + + // Act + site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); + + // Assert + Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsSection).Count()); + } + + [Theory] + [InlineData(testSite1PathCONST, 5)] + [InlineData(testSite2PathCONST, 8)] + [InlineData(testSite3PathCONST, 9)] + public void Home_ShouldGenereteCorrectQuantityOfPages(string sitePath, int expectedQuantity) + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); + + // Act + site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); + + // Assert + Assert.Equal(expectedQuantity, site.PagesReferences.Count); + } + + [Theory] + [InlineData(testSite1PathCONST, 4)] + [InlineData(testSite2PathCONST, 7)] + [InlineData(testSite3PathCONST, 7)] + public void Home_ShouldReturnIsPage(string sitePath, int expectedQuantity) + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); + + // Act + site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); + + // Assert + Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsPage).Count()); + } + [Theory] [InlineData("test1", Kind.index, "base", "Test Content 1")] [InlineData("test2", Kind.single, "content", "Test Content 2")] diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs index 6da78f0..13bab2f 100644 --- a/test/Parser/YAMLParserTests.cs +++ b/test/Parser/YAMLParserTests.cs @@ -4,7 +4,6 @@ using SuCoS.Parser; using System.Globalization; using SuCoS.Helpers; using SuCoS.Models; -using Microsoft.VisualBasic; namespace Test.Parser; @@ -82,19 +81,12 @@ Date: 2023-04-01 public void ParseFrontmatter_ShouldParseTitleCorrectly(string fileContent, string expectedTitle) { // Act - var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown(filePathCONST, fileContent); // Asset Assert.Equal(expectedTitle, frontMatter.Title); } - [Fact] - public void ParseFrontmatter_ShouldThrowException_WhenSiteIsNull() - { - // Asset - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(null!, filePathCONST, pageContent)); - } - [Theory] [InlineData(@"--- Date: 2023-01-01 @@ -109,7 +101,7 @@ Date: 2023/01/01 var expectedDate = DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); // Act - var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown(filePathCONST, fileContent); // Asset Assert.Equal(expectedDate, frontMatter.Date); @@ -124,7 +116,7 @@ Date: 2023/01/01 var expectedExpiryDate = DateTime.Parse("2024-06-01", CultureInfo.InvariantCulture); // Act - var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, pageContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown(filePathCONST, pageContent); // Asset Assert.Equal("Test Title", frontMatter.Title); @@ -144,7 +136,7 @@ Title "; // Asset - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent)); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(filePathCONST, fileContent)); } [Fact] @@ -175,10 +167,11 @@ Title public void ParseFrontmatter_ShouldParseContentInSiteFolder() { var date = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); - var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "", pageContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown("", pageContent); + Page page = new(frontMatter, siteDefault.Object); // Act - siteDefault.Object.PostProcessPage(frontMatter); + siteDefault.Object.PostProcessPage(page); // Asset Assert.Equal(date, frontMatter.Date); @@ -188,17 +181,20 @@ Title public void ParseFrontmatter_ShouldCreateTags() { // Act - var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "", pageContent); - siteDefault.Object.PostProcessPage(frontMatter); + var frontMatter = parser.ParseFrontmatterAndMarkdown("", pageContent); + Page page = new(frontMatter, siteDefault.Object); + + // Act + siteDefault.Object.PostProcessPage(page); // Asset - Assert.Equal(2, frontMatter.TagsReference?.Count); + Assert.Equal(2, page.TagsReference?.Count); } [Fact] public void ParseFrontmatter_ShouldParseCategoriesCorrectly() { - var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", pageContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown("fakeFilePath", pageContent); // Asset Assert.Equal(new[] { "Test", "Real Data" }, frontMatter.Params["Categories"]); @@ -215,31 +211,31 @@ Title [Fact] public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathIsNull() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(siteDefault.Object, null!)); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(null!)); } [Fact] public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathDoesNotExist() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile(siteDefault.Object, "fakePath")); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdownFromFile("fakePath")); } [Fact] public void ParseFrontmatter_ShouldThrowExceptionWhenFilePathDoesNotExist2() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, null!, "fakeContent")); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(null!, "fakeContent")); } [Fact] public void ParseFrontmatter_ShouldHandleEmptyFileContent() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", "")); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown("fakeFilePath", "")); } [Fact] public void ParseYAML_ShouldThrowExceptionWhenFrontmatterIsInvalid() { - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", "invalidFrontmatter")); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown("fakeFilePath", "invalidFrontmatter")); } [Fact] @@ -254,7 +250,7 @@ Title [Fact] public void ParseSiteSettings_ShouldReturnContent() { - var frontMatter = parser.ParseFrontmatterAndMarkdown(siteDefault.Object, "fakeFilePath", pageContent); + var frontMatter = parser.ParseFrontmatterAndMarkdown("fakeFilePath", pageContent); Assert.Equal(pageMarkdownCONST, frontMatter.RawContent); } -- GitLab From 16cefa81dde8c5167430eaec678cc8be02ee81e2 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Tue, 11 Jul 2023 16:56:46 -0300 Subject: [PATCH 4/9] test: theme tests, including failing --- source/Models/FrontMatter.cs | 17 -- source/Models/Page.cs | 7 +- source/Models/Site.cs | 75 ++++-- .../content/blog/weight-negative-1.md | 6 + .../content/blog/weight-negative-100.md | 6 + .../content/blog/weight-positive-1.md | 6 + .../content/blog/weight-positive-100.md | 6 + .../04-tags/content/blog/categories.md | 6 + .../04-tags/content/blog/date-future.md | 8 + .../04-tags/content/blog/date-ok.md | 8 + .../04-tags/content/blog/expired.md | 8 + .../content/blog/publishdate-future.md | 8 + .../04-tags/content/blog/publishdate-ok.md | 8 + .../04-tags/content/blog/subsection/alias.md | 8 + .../04-tags/content/blog/tags-01.md | 8 + .../04-tags/content/blog/tags-02.md | 8 + .../.TestSites/04-tags/content/blog/test01.md | 5 + .../04-tags/content/blog/weight-negative-1.md | 6 + .../content/blog/weight-negative-100.md | 6 + .../04-tags/content/blog/weight-positive-1.md | 6 + .../content/blog/weight-positive-100.md | 6 + test/.TestSites/04-tags/content/index.md | 5 + .../05-theme-no-baseof/content/blog/alias.md | 8 + .../content/blog/categories.md | 6 + .../content/blog/date-future.md | 8 + .../content/blog/date-ok.md | 8 + .../content/blog/expired.md | 8 + .../content/blog/publishdate-future.md | 8 + .../content/blog/publishdate-ok.md | 8 + .../content/blog/tags-01.md | 8 + .../content/blog/tags-02.md | 8 + .../05-theme-no-baseof/content/blog/test01.md | 5 + .../content/blog/weight-negative-1.md | 6 + .../content/blog/weight-negative-100.md | 6 + .../content/blog/weight-positive-1.md | 6 + .../content/blog/weight-positive-100.md | 6 + .../05-theme-no-baseof/content/index.md | 5 + test/.TestSites/05-theme-no-baseof/index.md | 5 + .../theme/_default/index.liquid | 1 + .../theme/_default/list.liquid | 1 + .../theme/_default/single.liquid | 1 + .../.TestSites/06-theme/content/blog/alias.md | 8 + .../06-theme/content/blog/categories.md | 6 + .../06-theme/content/blog/date-future.md | 8 + .../06-theme/content/blog/date-ok.md | 8 + .../06-theme/content/blog/expired.md | 8 + .../content/blog/publishdate-future.md | 8 + .../06-theme/content/blog/publishdate-ok.md | 8 + .../06-theme/content/blog/tags-01.md | 8 + .../06-theme/content/blog/tags-02.md | 8 + .../06-theme/content/blog/test01.md | 5 + .../content/blog/weight-negative-1.md | 6 + .../content/blog/weight-negative-100.md | 6 + .../content/blog/weight-positive-1.md | 6 + .../content/blog/weight-positive-100.md | 6 + test/.TestSites/06-theme/content/index.md | 5 + test/.TestSites/06-theme/index.md | 5 + .../06-theme/theme/_default/baseof.liquid | 1 + .../06-theme/theme/_default/index.liquid | 1 + .../06-theme/theme/_default/list.liquid | 1 + .../06-theme/theme/_default/single.liquid | 1 + .../content/blog/alias.md | 8 + .../content/blog/categories.md | 6 + .../content/blog/date-future.md | 8 + .../content/blog/date-ok.md | 8 + .../content/blog/expired.md | 8 + .../content/blog/publishdate-future.md | 8 + .../content/blog/publishdate-ok.md | 8 + .../content/blog/tags-01.md | 8 + .../content/blog/tags-02.md | 8 + .../content/blog/test01.md | 5 + .../content/blog/weight-negative-1.md | 6 + .../content/blog/weight-negative-100.md | 6 + .../content/blog/weight-positive-1.md | 6 + .../content/blog/weight-positive-100.md | 6 + .../07-theme-no-baseof-error/content/index.md | 5 + .../07-theme-no-baseof-error/index.md | 5 + .../theme/_default/index.liquid | 1 + .../theme/_default/list.liquid | 1 + .../theme/_default/single.liquid | 1 + test/Models/FrontMatterTests.cs | 22 +- test/Models/SiteTests.cs | 232 ++++++++++++++++-- 82 files changed, 733 insertions(+), 85 deletions(-) create mode 100644 test/.TestSites/03-section/content/blog/weight-negative-1.md create mode 100644 test/.TestSites/03-section/content/blog/weight-negative-100.md create mode 100644 test/.TestSites/03-section/content/blog/weight-positive-1.md create mode 100644 test/.TestSites/03-section/content/blog/weight-positive-100.md create mode 100644 test/.TestSites/04-tags/content/blog/categories.md create mode 100644 test/.TestSites/04-tags/content/blog/date-future.md create mode 100644 test/.TestSites/04-tags/content/blog/date-ok.md create mode 100644 test/.TestSites/04-tags/content/blog/expired.md create mode 100644 test/.TestSites/04-tags/content/blog/publishdate-future.md create mode 100644 test/.TestSites/04-tags/content/blog/publishdate-ok.md create mode 100644 test/.TestSites/04-tags/content/blog/subsection/alias.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-01.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-02.md create mode 100644 test/.TestSites/04-tags/content/blog/test01.md create mode 100644 test/.TestSites/04-tags/content/blog/weight-negative-1.md create mode 100644 test/.TestSites/04-tags/content/blog/weight-negative-100.md create mode 100644 test/.TestSites/04-tags/content/blog/weight-positive-1.md create mode 100644 test/.TestSites/04-tags/content/blog/weight-positive-100.md create mode 100644 test/.TestSites/04-tags/content/index.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/alias.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/categories.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/date-future.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/date-ok.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/expired.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/publishdate-future.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/publishdate-ok.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/tags-01.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/tags-02.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/test01.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-1.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-100.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-1.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-100.md create mode 100644 test/.TestSites/05-theme-no-baseof/content/index.md create mode 100644 test/.TestSites/05-theme-no-baseof/index.md create mode 100644 test/.TestSites/05-theme-no-baseof/theme/_default/index.liquid create mode 100644 test/.TestSites/05-theme-no-baseof/theme/_default/list.liquid create mode 100644 test/.TestSites/05-theme-no-baseof/theme/_default/single.liquid create mode 100644 test/.TestSites/06-theme/content/blog/alias.md create mode 100644 test/.TestSites/06-theme/content/blog/categories.md create mode 100644 test/.TestSites/06-theme/content/blog/date-future.md create mode 100644 test/.TestSites/06-theme/content/blog/date-ok.md create mode 100644 test/.TestSites/06-theme/content/blog/expired.md create mode 100644 test/.TestSites/06-theme/content/blog/publishdate-future.md create mode 100644 test/.TestSites/06-theme/content/blog/publishdate-ok.md create mode 100644 test/.TestSites/06-theme/content/blog/tags-01.md create mode 100644 test/.TestSites/06-theme/content/blog/tags-02.md create mode 100644 test/.TestSites/06-theme/content/blog/test01.md create mode 100644 test/.TestSites/06-theme/content/blog/weight-negative-1.md create mode 100644 test/.TestSites/06-theme/content/blog/weight-negative-100.md create mode 100644 test/.TestSites/06-theme/content/blog/weight-positive-1.md create mode 100644 test/.TestSites/06-theme/content/blog/weight-positive-100.md create mode 100644 test/.TestSites/06-theme/content/index.md create mode 100644 test/.TestSites/06-theme/index.md create mode 100644 test/.TestSites/06-theme/theme/_default/baseof.liquid create mode 100644 test/.TestSites/06-theme/theme/_default/index.liquid create mode 100644 test/.TestSites/06-theme/theme/_default/list.liquid create mode 100644 test/.TestSites/06-theme/theme/_default/single.liquid create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/alias.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/categories.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/date-future.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/date-ok.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/expired.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-future.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-ok.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/tags-01.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/tags-02.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/test01.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-1.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-100.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-1.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-100.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/content/index.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/index.md create mode 100644 test/.TestSites/07-theme-no-baseof-error/theme/_default/index.liquid create mode 100644 test/.TestSites/07-theme-no-baseof-error/theme/_default/list.liquid create mode 100644 test/.TestSites/07-theme-no-baseof-error/theme/_default/single.liquid diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs index 3a19ccf..2ca8bc4 100644 --- a/source/Models/FrontMatter.cs +++ b/source/Models/FrontMatter.cs @@ -82,23 +82,6 @@ public class FrontMatter : IFrontMatter, IParams /// public FrontMatter() { } - /// - /// Constructor - /// - /// - /// - /// - /// - /// - public FrontMatter(string title, string section, string type, string url, Kind kind = Kind.list) - { - Title = title; - Section = section; - Kind = kind; - Type = type; - URL = url; - } - /// /// Constructor /// diff --git a/source/Models/Page.cs b/source/Models/Page.cs index 4b1af5c..f9d1789 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -84,7 +84,10 @@ public class Page : IFrontMatter /// The source directory of the file. /// [YamlIgnore] - public string? SourcePathLastDirectory => new DirectoryInfo(SourcePathDirectory ?? string.Empty).Name; + public string? SourcePathLastDirectory => string.IsNullOrEmpty(SourcePathDirectory) + ? null + : Path.GetFileName(Path.GetFullPath(SourcePathDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); + /// /// Point to the site configuration. @@ -128,7 +131,7 @@ public class Page : IFrontMatter /// A list of tags, if any. /// [YamlIgnore] - public List? TagsReference { get; set; } + public ConcurrentBag? TagsReference { get; set; } /// /// Just a simple check if the current page is the home page diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 1fb8e77..888f470 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -80,7 +80,9 @@ public class Site : IParams { get { - pagesCache ??= PagesReferences.Values.ToList(); + pagesCache ??= PagesReferences.Values + .OrderBy(page => -page.Weight) + .ToList(); return pagesCache!; } } @@ -108,8 +110,9 @@ public class Site : IParams get { regularPagesCache ??= PagesReferences - .Where(pair => pair.Value.Kind == Kind.single && pair.Key == pair.Value.Permalink) + .Where(pair => pair.Value.IsPage && pair.Key == pair.Value.Permalink) .Select(pair => pair.Value) + .OrderBy(page => -page.Weight) .ToList(); return regularPagesCache; } @@ -235,9 +238,9 @@ public class Site : IParams /// /// Folder to scan /// Folder recursive level - /// Page of the upper directory + /// Page of the upper directory /// - public void ParseAndScanSourceFiles(string directory, int level = 0, Page? pageParent = null) + public void ParseAndScanSourceFiles(string? directory, int level = 0, Page? parent = null) { directory ??= SourceContentPath; @@ -247,7 +250,7 @@ public class Site : IParams if (indexPath != null) { markdownFiles = markdownFiles.Where(file => file != indexPath).ToArray(); - var page = ParseSourceFile(pageParent, indexPath); + var page = ParseSourceFile(parent, indexPath); if (level == 0) { PagesReferences.Remove(page!.Permalink!); @@ -258,7 +261,7 @@ public class Site : IParams } else { - pageParent = page; + parent = page; } } else if (level == 0) @@ -269,24 +272,18 @@ public class Site : IParams else if (level == 1) { var section = new DirectoryInfo(directory).Name; - var contentTemplate = new FrontMatter( - title: section, - section: "section", - type: "section", - url: section - ); - pageParent = CreateSystemPage(contentTemplate, null); + parent = CreateSectionPage(section); } _ = Parallel.ForEach(markdownFiles, filePath => { - ParseSourceFile(pageParent, filePath); + ParseSourceFile(parent, filePath); }); var subdirectories = Directory.GetDirectories(directory); foreach (var subdirectory in subdirectories) { - ParseAndScanSourceFiles(subdirectory, level + 1, pageParent); + ParseAndScanSourceFiles(subdirectory, level + 1, parent); } } @@ -315,6 +312,20 @@ public class Site : IParams return page; } + private Page CreateSectionPage(string section) + { + var contentTemplate = new FrontMatter() + { + Title = section, + Section = "section", + Kind = Kind.list, + Type = "section", + URL = section, + SourcePath = section + }; + return CreateSystemPage(contentTemplate, null); + } + /// /// Create a page not from the content folder, but as part of the process. /// It's used to create tag pages, section list pages, etc. @@ -332,9 +343,18 @@ public class Site : IParams { if (!automaticContentCache.TryGetValue(key: id, out page)) { + Page? parent = null; + + // Check if we need to create a section, even + var sections = (frontMatter.SourcePath ?? string.Empty).Split('/', StringSplitOptions.RemoveEmptyEntries); + if (sections.Count() > 1) + { + parent = CreateSectionPage(sections[0]); + } + page = new(frontMatter, this); automaticContentCache.Add(id, page); - PostProcessPage(page); + PostProcessPage(page, parent); } } @@ -349,11 +369,9 @@ public class Site : IParams { return page; } - lock (originalPage) - { - originalPage.TagsReference ??= new(); - originalPage.TagsReference!.Add(page); - } + + originalPage.TagsReference ??= new(); + originalPage.TagsReference!.Add(page); return page; } @@ -427,12 +445,15 @@ public class Site : IParams { foreach (var tagName in page.Tags) { - FrontMatter contentTemplate = new( - title: tagName, - section: "tags", - type: "tags", - url: "tags/" + Urlizer.Urlize(tagName) - ); + FrontMatter contentTemplate = new() + { + Title = tagName, + Section = "tags", + Type = "tags", + Kind = Kind.list, + URL = "tags/" + Urlizer.Urlize(tagName), + SourcePath = "tags/" + tagName + }; CreateSystemPage(contentTemplate, page); } } diff --git a/test/.TestSites/03-section/content/blog/weight-negative-1.md b/test/.TestSites/03-section/content/blog/weight-negative-1.md new file mode 100644 index 0000000..974d2c9 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/weight-negative-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -1" +Weight: -1 +--- + +Test Content 1 diff --git a/test/.TestSites/03-section/content/blog/weight-negative-100.md b/test/.TestSites/03-section/content/blog/weight-negative-100.md new file mode 100644 index 0000000..e36b408 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/weight-negative-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -100" +Weight: -100 +--- + +Test Content 1 diff --git a/test/.TestSites/03-section/content/blog/weight-positive-1.md b/test/.TestSites/03-section/content/blog/weight-positive-1.md new file mode 100644 index 0000000..2355b30 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/weight-positive-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 1" +Weight: 1 +--- + +Test Content 1 diff --git a/test/.TestSites/03-section/content/blog/weight-positive-100.md b/test/.TestSites/03-section/content/blog/weight-positive-100.md new file mode 100644 index 0000000..08cfa38 --- /dev/null +++ b/test/.TestSites/03-section/content/blog/weight-positive-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 100" +Weight: 100 +--- + +Test Content 1 diff --git a/test/.TestSites/04-tags/content/blog/categories.md b/test/.TestSites/04-tags/content/blog/categories.md new file mode 100644 index 0000000..4065d67 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/categories.md @@ -0,0 +1,6 @@ +--- +Title: Categories +Categories: ['Test', 'Real Data'] +--- + +Categories diff --git a/test/.TestSites/04-tags/content/blog/date-future.md b/test/.TestSites/04-tags/content/blog/date-future.md new file mode 100644 index 0000000..5a6a90e --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/date-future.md @@ -0,0 +1,8 @@ +--- +Title: Date Future +Date: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/04-tags/content/blog/date-ok.md b/test/.TestSites/04-tags/content/blog/date-ok.md new file mode 100644 index 0000000..d4363e7 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/date-ok.md @@ -0,0 +1,8 @@ +--- +Title: Date-OK +Date: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/04-tags/content/blog/expired.md b/test/.TestSites/04-tags/content/blog/expired.md new file mode 100644 index 0000000..02ae1bb --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/expired.md @@ -0,0 +1,8 @@ +--- +Title: Expired +ExpiryDate: 2020-04-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/04-tags/content/blog/publishdate-future.md b/test/.TestSites/04-tags/content/blog/publishdate-future.md new file mode 100644 index 0000000..70e8da6 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/publishdate-future.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate Future +PublishDate: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/04-tags/content/blog/publishdate-ok.md b/test/.TestSites/04-tags/content/blog/publishdate-ok.md new file mode 100644 index 0000000..f4507ff --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/publishdate-ok.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate OK +PublishDate: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/04-tags/content/blog/subsection/alias.md b/test/.TestSites/04-tags/content/blog/subsection/alias.md new file mode 100644 index 0000000..44e58b4 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/subsection/alias.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Aliases: + - v123 + - "{{ page.Title }}-2" +--- + +Test Alias diff --git a/test/.TestSites/04-tags/content/blog/tags-01.md b/test/.TestSites/04-tags/content/blog/tags-01.md new file mode 100644 index 0000000..0f79798 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-01.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Tags: + - tag1 + - tag 2 +--- + +Test Alias diff --git a/test/.TestSites/04-tags/content/blog/tags-02.md b/test/.TestSites/04-tags/content/blog/tags-02.md new file mode 100644 index 0000000..afcf002 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-02.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias 2 +Tags: + - tag1 + - tag 2 +--- + +Test Alias diff --git a/test/.TestSites/04-tags/content/blog/test01.md b/test/.TestSites/04-tags/content/blog/test01.md new file mode 100644 index 0000000..2d3c415 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/test01.md @@ -0,0 +1,5 @@ +--- +Title: Test Content 1 +--- + +Test Content 1 diff --git a/test/.TestSites/04-tags/content/blog/weight-negative-1.md b/test/.TestSites/04-tags/content/blog/weight-negative-1.md new file mode 100644 index 0000000..974d2c9 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/weight-negative-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -1" +Weight: -1 +--- + +Test Content 1 diff --git a/test/.TestSites/04-tags/content/blog/weight-negative-100.md b/test/.TestSites/04-tags/content/blog/weight-negative-100.md new file mode 100644 index 0000000..e36b408 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/weight-negative-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -100" +Weight: -100 +--- + +Test Content 1 diff --git a/test/.TestSites/04-tags/content/blog/weight-positive-1.md b/test/.TestSites/04-tags/content/blog/weight-positive-1.md new file mode 100644 index 0000000..2355b30 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/weight-positive-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 1" +Weight: 1 +--- + +Test Content 1 diff --git a/test/.TestSites/04-tags/content/blog/weight-positive-100.md b/test/.TestSites/04-tags/content/blog/weight-positive-100.md new file mode 100644 index 0000000..08cfa38 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/weight-positive-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 100" +Weight: 100 +--- + +Test Content 1 diff --git a/test/.TestSites/04-tags/content/index.md b/test/.TestSites/04-tags/content/index.md new file mode 100644 index 0000000..28806b1 --- /dev/null +++ b/test/.TestSites/04-tags/content/index.md @@ -0,0 +1,5 @@ +--- +Title: My Home Page +--- + +Index Content diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/alias.md b/test/.TestSites/05-theme-no-baseof/content/blog/alias.md new file mode 100644 index 0000000..44e58b4 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/alias.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Aliases: + - v123 + - "{{ page.Title }}-2" +--- + +Test Alias diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/categories.md b/test/.TestSites/05-theme-no-baseof/content/blog/categories.md new file mode 100644 index 0000000..4065d67 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/categories.md @@ -0,0 +1,6 @@ +--- +Title: Categories +Categories: ['Test', 'Real Data'] +--- + +Categories diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/date-future.md b/test/.TestSites/05-theme-no-baseof/content/blog/date-future.md new file mode 100644 index 0000000..5a6a90e --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/date-future.md @@ -0,0 +1,8 @@ +--- +Title: Date Future +Date: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/date-ok.md b/test/.TestSites/05-theme-no-baseof/content/blog/date-ok.md new file mode 100644 index 0000000..d4363e7 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/date-ok.md @@ -0,0 +1,8 @@ +--- +Title: Date-OK +Date: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/expired.md b/test/.TestSites/05-theme-no-baseof/content/blog/expired.md new file mode 100644 index 0000000..02ae1bb --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/expired.md @@ -0,0 +1,8 @@ +--- +Title: Expired +ExpiryDate: 2020-04-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/publishdate-future.md b/test/.TestSites/05-theme-no-baseof/content/blog/publishdate-future.md new file mode 100644 index 0000000..70e8da6 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/publishdate-future.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate Future +PublishDate: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/publishdate-ok.md b/test/.TestSites/05-theme-no-baseof/content/blog/publishdate-ok.md new file mode 100644 index 0000000..f4507ff --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/publishdate-ok.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate OK +PublishDate: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/tags-01.md b/test/.TestSites/05-theme-no-baseof/content/blog/tags-01.md new file mode 100644 index 0000000..0f79798 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/tags-01.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Tags: + - tag1 + - tag 2 +--- + +Test Alias diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/tags-02.md b/test/.TestSites/05-theme-no-baseof/content/blog/tags-02.md new file mode 100644 index 0000000..afcf002 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/tags-02.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias 2 +Tags: + - tag1 + - tag 2 +--- + +Test Alias diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/test01.md b/test/.TestSites/05-theme-no-baseof/content/blog/test01.md new file mode 100644 index 0000000..2d3c415 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/test01.md @@ -0,0 +1,5 @@ +--- +Title: Test Content 1 +--- + +Test Content 1 diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-1.md b/test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-1.md new file mode 100644 index 0000000..974d2c9 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -1" +Weight: -1 +--- + +Test Content 1 diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-100.md b/test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-100.md new file mode 100644 index 0000000..e36b408 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/weight-negative-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -100" +Weight: -100 +--- + +Test Content 1 diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-1.md b/test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-1.md new file mode 100644 index 0000000..2355b30 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 1" +Weight: 1 +--- + +Test Content 1 diff --git a/test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-100.md b/test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-100.md new file mode 100644 index 0000000..08cfa38 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/blog/weight-positive-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 100" +Weight: 100 +--- + +Test Content 1 diff --git a/test/.TestSites/05-theme-no-baseof/content/index.md b/test/.TestSites/05-theme-no-baseof/content/index.md new file mode 100644 index 0000000..28806b1 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/content/index.md @@ -0,0 +1,5 @@ +--- +Title: My Home Page +--- + +Index Content diff --git a/test/.TestSites/05-theme-no-baseof/index.md b/test/.TestSites/05-theme-no-baseof/index.md new file mode 100644 index 0000000..28806b1 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/index.md @@ -0,0 +1,5 @@ +--- +Title: My Home Page +--- + +Index Content diff --git a/test/.TestSites/05-theme-no-baseof/theme/_default/index.liquid b/test/.TestSites/05-theme-no-baseof/theme/_default/index.liquid new file mode 100644 index 0000000..6b9b3b0 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/theme/_default/index.liquid @@ -0,0 +1 @@ +INDEX-{{ page.ContentPreRendered }} \ No newline at end of file diff --git a/test/.TestSites/05-theme-no-baseof/theme/_default/list.liquid b/test/.TestSites/05-theme-no-baseof/theme/_default/list.liquid new file mode 100644 index 0000000..df270cf --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/theme/_default/list.liquid @@ -0,0 +1 @@ +LIST-{{ page.ContentPreRendered }} \ No newline at end of file diff --git a/test/.TestSites/05-theme-no-baseof/theme/_default/single.liquid b/test/.TestSites/05-theme-no-baseof/theme/_default/single.liquid new file mode 100644 index 0000000..bea53d9 --- /dev/null +++ b/test/.TestSites/05-theme-no-baseof/theme/_default/single.liquid @@ -0,0 +1 @@ +SINGLE-{{ page.ContentPreRendered }} \ No newline at end of file diff --git a/test/.TestSites/06-theme/content/blog/alias.md b/test/.TestSites/06-theme/content/blog/alias.md new file mode 100644 index 0000000..44e58b4 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/alias.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Aliases: + - v123 + - "{{ page.Title }}-2" +--- + +Test Alias diff --git a/test/.TestSites/06-theme/content/blog/categories.md b/test/.TestSites/06-theme/content/blog/categories.md new file mode 100644 index 0000000..4065d67 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/categories.md @@ -0,0 +1,6 @@ +--- +Title: Categories +Categories: ['Test', 'Real Data'] +--- + +Categories diff --git a/test/.TestSites/06-theme/content/blog/date-future.md b/test/.TestSites/06-theme/content/blog/date-future.md new file mode 100644 index 0000000..5a6a90e --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/date-future.md @@ -0,0 +1,8 @@ +--- +Title: Date Future +Date: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/06-theme/content/blog/date-ok.md b/test/.TestSites/06-theme/content/blog/date-ok.md new file mode 100644 index 0000000..d4363e7 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/date-ok.md @@ -0,0 +1,8 @@ +--- +Title: Date-OK +Date: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/06-theme/content/blog/expired.md b/test/.TestSites/06-theme/content/blog/expired.md new file mode 100644 index 0000000..02ae1bb --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/expired.md @@ -0,0 +1,8 @@ +--- +Title: Expired +ExpiryDate: 2020-04-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/06-theme/content/blog/publishdate-future.md b/test/.TestSites/06-theme/content/blog/publishdate-future.md new file mode 100644 index 0000000..70e8da6 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/publishdate-future.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate Future +PublishDate: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/06-theme/content/blog/publishdate-ok.md b/test/.TestSites/06-theme/content/blog/publishdate-ok.md new file mode 100644 index 0000000..f4507ff --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/publishdate-ok.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate OK +PublishDate: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/06-theme/content/blog/tags-01.md b/test/.TestSites/06-theme/content/blog/tags-01.md new file mode 100644 index 0000000..0f79798 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/tags-01.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Tags: + - tag1 + - tag 2 +--- + +Test Alias diff --git a/test/.TestSites/06-theme/content/blog/tags-02.md b/test/.TestSites/06-theme/content/blog/tags-02.md new file mode 100644 index 0000000..afcf002 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/tags-02.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias 2 +Tags: + - tag1 + - tag 2 +--- + +Test Alias diff --git a/test/.TestSites/06-theme/content/blog/test01.md b/test/.TestSites/06-theme/content/blog/test01.md new file mode 100644 index 0000000..2d3c415 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/test01.md @@ -0,0 +1,5 @@ +--- +Title: Test Content 1 +--- + +Test Content 1 diff --git a/test/.TestSites/06-theme/content/blog/weight-negative-1.md b/test/.TestSites/06-theme/content/blog/weight-negative-1.md new file mode 100644 index 0000000..974d2c9 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/weight-negative-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -1" +Weight: -1 +--- + +Test Content 1 diff --git a/test/.TestSites/06-theme/content/blog/weight-negative-100.md b/test/.TestSites/06-theme/content/blog/weight-negative-100.md new file mode 100644 index 0000000..e36b408 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/weight-negative-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -100" +Weight: -100 +--- + +Test Content 1 diff --git a/test/.TestSites/06-theme/content/blog/weight-positive-1.md b/test/.TestSites/06-theme/content/blog/weight-positive-1.md new file mode 100644 index 0000000..2355b30 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/weight-positive-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 1" +Weight: 1 +--- + +Test Content 1 diff --git a/test/.TestSites/06-theme/content/blog/weight-positive-100.md b/test/.TestSites/06-theme/content/blog/weight-positive-100.md new file mode 100644 index 0000000..08cfa38 --- /dev/null +++ b/test/.TestSites/06-theme/content/blog/weight-positive-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 100" +Weight: 100 +--- + +Test Content 1 diff --git a/test/.TestSites/06-theme/content/index.md b/test/.TestSites/06-theme/content/index.md new file mode 100644 index 0000000..28806b1 --- /dev/null +++ b/test/.TestSites/06-theme/content/index.md @@ -0,0 +1,5 @@ +--- +Title: My Home Page +--- + +Index Content diff --git a/test/.TestSites/06-theme/index.md b/test/.TestSites/06-theme/index.md new file mode 100644 index 0000000..28806b1 --- /dev/null +++ b/test/.TestSites/06-theme/index.md @@ -0,0 +1,5 @@ +--- +Title: My Home Page +--- + +Index Content diff --git a/test/.TestSites/06-theme/theme/_default/baseof.liquid b/test/.TestSites/06-theme/theme/_default/baseof.liquid new file mode 100644 index 0000000..e6f3bed --- /dev/null +++ b/test/.TestSites/06-theme/theme/_default/baseof.liquid @@ -0,0 +1 @@ +BASEOF-{{ page.Content }} \ No newline at end of file diff --git a/test/.TestSites/06-theme/theme/_default/index.liquid b/test/.TestSites/06-theme/theme/_default/index.liquid new file mode 100644 index 0000000..6b9b3b0 --- /dev/null +++ b/test/.TestSites/06-theme/theme/_default/index.liquid @@ -0,0 +1 @@ +INDEX-{{ page.ContentPreRendered }} \ No newline at end of file diff --git a/test/.TestSites/06-theme/theme/_default/list.liquid b/test/.TestSites/06-theme/theme/_default/list.liquid new file mode 100644 index 0000000..df270cf --- /dev/null +++ b/test/.TestSites/06-theme/theme/_default/list.liquid @@ -0,0 +1 @@ +LIST-{{ page.ContentPreRendered }} \ No newline at end of file diff --git a/test/.TestSites/06-theme/theme/_default/single.liquid b/test/.TestSites/06-theme/theme/_default/single.liquid new file mode 100644 index 0000000..bea53d9 --- /dev/null +++ b/test/.TestSites/06-theme/theme/_default/single.liquid @@ -0,0 +1 @@ +SINGLE-{{ page.ContentPreRendered }} \ No newline at end of file diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/alias.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/alias.md new file mode 100644 index 0000000..44e58b4 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/alias.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Aliases: + - v123 + - "{{ page.Title }}-2" +--- + +Test Alias diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/categories.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/categories.md new file mode 100644 index 0000000..4065d67 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/categories.md @@ -0,0 +1,6 @@ +--- +Title: Categories +Categories: ['Test', 'Real Data'] +--- + +Categories diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/date-future.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/date-future.md new file mode 100644 index 0000000..5a6a90e --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/date-future.md @@ -0,0 +1,8 @@ +--- +Title: Date Future +Date: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/date-ok.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/date-ok.md new file mode 100644 index 0000000..d4363e7 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/date-ok.md @@ -0,0 +1,8 @@ +--- +Title: Date-OK +Date: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/expired.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/expired.md new file mode 100644 index 0000000..02ae1bb --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/expired.md @@ -0,0 +1,8 @@ +--- +Title: Expired +ExpiryDate: 2020-04-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-future.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-future.md new file mode 100644 index 0000000..70e8da6 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-future.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate Future +PublishDate: 2023-07-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-ok.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-ok.md new file mode 100644 index 0000000..f4507ff --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/publishdate-ok.md @@ -0,0 +1,8 @@ +--- +Title: PublishDate OK +PublishDate: 2023-01-01 +--- + +## Real Data Test + +This is a test using real data.", "Real Data Test diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/tags-01.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/tags-01.md new file mode 100644 index 0000000..0f79798 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/tags-01.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias +Tags: + - tag1 + - tag 2 +--- + +Test Alias diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/tags-02.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/tags-02.md new file mode 100644 index 0000000..afcf002 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/tags-02.md @@ -0,0 +1,8 @@ +--- +Title: Test Alias 2 +Tags: + - tag1 + - tag 2 +--- + +Test Alias diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/test01.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/test01.md new file mode 100644 index 0000000..2d3c415 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/test01.md @@ -0,0 +1,5 @@ +--- +Title: Test Content 1 +--- + +Test Content 1 diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-1.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-1.md new file mode 100644 index 0000000..974d2c9 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -1" +Weight: -1 +--- + +Test Content 1 diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-100.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-100.md new file mode 100644 index 0000000..e36b408 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-negative-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: -100" +Weight: -100 +--- + +Test Content 1 diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-1.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-1.md new file mode 100644 index 0000000..2355b30 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-1.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 1" +Weight: 1 +--- + +Test Content 1 diff --git a/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-100.md b/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-100.md new file mode 100644 index 0000000..08cfa38 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/blog/weight-positive-100.md @@ -0,0 +1,6 @@ +--- +Title: "Weight: plus 100" +Weight: 100 +--- + +Test Content 1 diff --git a/test/.TestSites/07-theme-no-baseof-error/content/index.md b/test/.TestSites/07-theme-no-baseof-error/content/index.md new file mode 100644 index 0000000..28806b1 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/content/index.md @@ -0,0 +1,5 @@ +--- +Title: My Home Page +--- + +Index Content diff --git a/test/.TestSites/07-theme-no-baseof-error/index.md b/test/.TestSites/07-theme-no-baseof-error/index.md new file mode 100644 index 0000000..28806b1 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/index.md @@ -0,0 +1,5 @@ +--- +Title: My Home Page +--- + +Index Content diff --git a/test/.TestSites/07-theme-no-baseof-error/theme/_default/index.liquid b/test/.TestSites/07-theme-no-baseof-error/theme/_default/index.liquid new file mode 100644 index 0000000..4a27853 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/theme/_default/index.liquid @@ -0,0 +1 @@ +INDEX-{% page.ContentPreRendered %} \ No newline at end of file diff --git a/test/.TestSites/07-theme-no-baseof-error/theme/_default/list.liquid b/test/.TestSites/07-theme-no-baseof-error/theme/_default/list.liquid new file mode 100644 index 0000000..e77db28 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/theme/_default/list.liquid @@ -0,0 +1 @@ +LIST-{% page.ContentPreRendered %} \ No newline at end of file diff --git a/test/.TestSites/07-theme-no-baseof-error/theme/_default/single.liquid b/test/.TestSites/07-theme-no-baseof-error/theme/_default/single.liquid new file mode 100644 index 0000000..bd6be74 --- /dev/null +++ b/test/.TestSites/07-theme-no-baseof-error/theme/_default/single.liquid @@ -0,0 +1 @@ +SINGLE-{% page.ContentPreRendered %} \ No newline at end of file diff --git a/test/Models/FrontMatterTests.cs b/test/Models/FrontMatterTests.cs index 5930e02..3f1c259 100644 --- a/test/Models/FrontMatterTests.cs +++ b/test/Models/FrontMatterTests.cs @@ -13,7 +13,14 @@ public class FrontMatterTests public void Constructor_Sets_Properties_Correctly(string title, string section, string type, string url, Kind kind) { // Act - var basicContent = new FrontMatter(title, section, type, url, kind); + var basicContent = new FrontMatter() + { + Title = title, + Section = section, + Type = type, + URL = url, + Kind = kind + }; // Assert Assert.Equal(title, basicContent.Title); @@ -23,19 +30,6 @@ public class FrontMatterTests Assert.Equal(kind, basicContent.Kind); } - [Fact] - public void Constructor_Sets_Kind_To_List_If_Not_Provided() - { - // Arrange - const string title = "Title1", section = "Section1", type = "Type1", url = "URL1"; - - // Act - var basicContent = new FrontMatter(title, section, type, url); - - // Assert - Assert.Equal(Kind.list, basicContent.Kind); - } - [Theory] [InlineData("C:/Test/Document.txt", "Document")] [InlineData("C:/Test/SubFolder/Document.txt", "Document")] diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs index 977f396..b4522b4 100644 --- a/test/Models/SiteTests.cs +++ b/test/Models/SiteTests.cs @@ -12,9 +12,13 @@ public class SiteTests { private readonly Site site; private readonly Mock systemClockMock = new(); - private const string testSite1PathCONST = ".TestSites/01"; - private const string testSite2PathCONST = ".TestSites/02-have-index"; - private const string testSite3PathCONST = ".TestSites/03-section"; + private const string testSitePathCONST01 = ".TestSites/01"; + private const string testSitePathCONST02 = ".TestSites/02-have-index"; + private const string testSitePathCONST03 = ".TestSites/03-section"; + private const string testSitePathCONST04 = ".TestSites/04-tags"; + private const string testSitePathCONST05 = ".TestSites/05-theme-no-baseof"; + private const string testSitePathCONST06 = ".TestSites/06-theme"; + private const string testSitePathCONST07 = ".TestSites/07-theme-no-baseof-error"; // based on the compiled test.dll path // that is typically "bin/Debug/netX.0/test.dll" @@ -30,28 +34,29 @@ public class SiteTests [Theory] [InlineData("test01.md")] [InlineData("date-ok.md")] - public void Test_ScanAllMarkdownFiles(string fileName) + public void ScanAllMarkdownFiles_ShouldCountainFilenames(string fileName) { var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSite1PathCONST)); + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST01)); // Act site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); // Assert - Assert.Contains(site.Pages, page => page.SourcePathDirectory?.Length == 0); + Assert.Contains(site.Pages, page => page.SourcePathDirectory!.Length == 0); Assert.Contains(site.Pages, page => page.SourceFileNameWithoutExtension == fileNameWithoutExtension); } [Theory] - [InlineData(testSite1PathCONST)] - [InlineData(testSite2PathCONST)] + [InlineData(testSitePathCONST01)] + [InlineData(testSitePathCONST02)] public void Home_ShouldReturnAHomePage(string sitePath) { var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); + site.SourceDirectoryPath = siteFullPath; // Act - site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); + site.ParseAndScanSourceFiles(site.SourceContentPath); // Assert Assert.NotNull(site.Home); @@ -60,45 +65,48 @@ public class SiteTests } [Theory] - [InlineData(testSite1PathCONST, 0)] - [InlineData(testSite2PathCONST, 0)] - [InlineData(testSite3PathCONST, 1)] - public void Home_ShouldReturnIsSection(string sitePath, int expectedQuantity) + [InlineData(testSitePathCONST01, 0)] + [InlineData(testSitePathCONST02, 0)] + [InlineData(testSitePathCONST03, 1)] + public void Page_IsSection_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); + site.SourceDirectoryPath = siteFullPath; // Act - site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); + site.ParseAndScanSourceFiles(null); // Assert Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsSection).Count()); } [Theory] - [InlineData(testSite1PathCONST, 5)] - [InlineData(testSite2PathCONST, 8)] - [InlineData(testSite3PathCONST, 9)] - public void Home_ShouldGenereteCorrectQuantityOfPages(string sitePath, int expectedQuantity) + [InlineData(testSitePathCONST01, 5)] + [InlineData(testSitePathCONST02, 8)] + [InlineData(testSitePathCONST03, 13)] + public void PagesReference_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); + site.SourceDirectoryPath = siteFullPath; // Act - site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); + site.ParseAndScanSourceFiles(null); // Assert Assert.Equal(expectedQuantity, site.PagesReferences.Count); } [Theory] - [InlineData(testSite1PathCONST, 4)] - [InlineData(testSite2PathCONST, 7)] - [InlineData(testSite3PathCONST, 7)] - public void Home_ShouldReturnIsPage(string sitePath, int expectedQuantity) + [InlineData(testSitePathCONST01, 4)] + [InlineData(testSitePathCONST02, 7)] + [InlineData(testSitePathCONST03, 11)] + public void Page_IsPage_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); + site.SourceDirectoryPath = siteFullPath; // Act - site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); + site.ParseAndScanSourceFiles(null); // Assert Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsPage).Count()); @@ -107,7 +115,7 @@ public class SiteTests [Theory] [InlineData("test1", Kind.index, "base", "Test Content 1")] [InlineData("test2", Kind.single, "content", "Test Content 2")] - public void Test_ResetCache(string firstKeyPart, Kind secondKeyPart, string thirdKeyPart, string value) + public void ResetCache_ShouldCachesBeEmpty(string firstKeyPart, Kind secondKeyPart, string thirdKeyPart, string value) { var key = (firstKeyPart, secondKeyPart, thirdKeyPart); site.baseTemplateCache.Add(key, value); @@ -120,4 +128,178 @@ public class SiteTests Assert.Empty(site.contentTemplateCache); Assert.Empty(site.PagesReferences); } + + [Fact] + public void Page_Weight_ShouldReturnTheRightOrder() + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST03)); + site.SourceDirectoryPath = siteFullPath; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + Assert.Equal(100, site.RegularPages.First().Weight); + Assert.Equal(-100, site.RegularPages.Last().Weight); + } + + [Fact] + public void Page_Weight_ShouldReturnZeroWeight() + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST02)); + site.SourceDirectoryPath = siteFullPath; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + Assert.Equal(0, site.RegularPages.First().Weight); + Assert.Equal(0, site.RegularPages.Last().Weight); + } + + [Fact] + public void TagSectionPage_Pages_ShouldReturnNumberTagPages() + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)); + site.SourceDirectoryPath = siteFullPath; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + site.PagesReferences.TryGetValue("/tags", out var tagSectionPage); + Assert.NotNull(tagSectionPage); + Assert.Equal(2, tagSectionPage.Pages.Count()); + Assert.Empty(tagSectionPage.RegularPages); + Assert.Equal("tags", tagSectionPage.SourcePath); + Assert.Equal(string.Empty, tagSectionPage.SourcePathDirectory); + Assert.Null(tagSectionPage.SourcePathLastDirectory); + } + + [Fact] + public void TagPage_Pages_ShouldReturnNumberReferences() + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)); + site.SourceDirectoryPath = siteFullPath; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + site.PagesReferences.TryGetValue("/tags/tag1", out var page); + Assert.NotNull(page); + Assert.Equal(2, page.Pages.Count()); + Assert.Equal(2, page.RegularPages.Count()); + } + + [Theory] + [InlineData("/", "

Index Content

\n")] + [InlineData("/blog", "")] + [InlineData("/tags", "")] + [InlineData("/tags/tag1", "")] + [InlineData("/blog/test-content-1", "

Test Content 1

\n")] + public void Page_Content_ShouldReturnNullThemeContent(string url, string expectedContent) + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)); + site.SourceDirectoryPath = siteFullPath; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + site.PagesReferences.TryGetValue(url, out var page); + Assert.NotNull(page); + Assert.Equal(expectedContent, page.Content); + Assert.Equal(page.ContentPreRendered, page.Content); + } + + [Theory] + [InlineData("/", + "

Index Content

\n", + "INDEX-

Index Content

\n")] + [InlineData("/blog", + "", + "LIST-")] + [InlineData("/tags", + "", + "LIST-")] + [InlineData("/tags/tag1", + "", + "LIST-")] + [InlineData("/blog/test-content-1", + "

Test Content 1

\n", + "SINGLE-

Test Content 1

\n")] + public void Page_Content_ShouldReturnNullThemeBaseofContent(string url, string expectedContentPreRendered, string expectedContent) + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST05)); + site.SourceDirectoryPath = siteFullPath; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + site.PagesReferences.TryGetValue(url, out var page); + Assert.NotNull(page); + Assert.Equal(expectedContentPreRendered, page.ContentPreRendered); + Assert.Equal(expectedContent, page.Content); + Assert.Equal(expectedContent, page.CreateOutputFile()); + } + + [Theory] + [InlineData("/")] + [InlineData("/blog")] + [InlineData("/tags")] + [InlineData("/tags/tag1")] + [InlineData("/blog/test-content-1")] + public void Page_Content_ShouldReturnThrowNullThemeBaseofContent(string url) + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST07)); + site.SourceDirectoryPath = siteFullPath; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + site.PagesReferences.TryGetValue(url, out var page); + Assert.NotNull(page); + Assert.Equal(string.Empty, page.Content); + Assert.Equal(string.Empty, page.CreateOutputFile()); + } + + [Theory] + [InlineData("/", + "

Index Content

\n", + "INDEX-

Index Content

\n", + "BASEOF-INDEX-

Index Content

\n")] + [InlineData("/blog", + "", + "LIST-", + "BASEOF-LIST-")] + [InlineData("/tags", + "", + "LIST-", + "BASEOF-LIST-")] + [InlineData("/tags/tag1", + "", + "LIST-", + "BASEOF-LIST-")] + [InlineData("/blog/test-content-1", + "

Test Content 1

\n", + "SINGLE-

Test Content 1

\n", + "BASEOF-SINGLE-

Test Content 1

\n")] + public void Page_Content_ShouldReturnThemeContent(string url, string expectedContentPreRendered, string expectedContent, string expectedOutputfile) + { + var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST06)); + site.SourceDirectoryPath = siteFullPath; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + site.PagesReferences.TryGetValue(url, out var page); + Assert.NotNull(page); + Assert.Equal(expectedContentPreRendered, page.ContentPreRendered); + Assert.Equal(expectedContent, page.Content); + Assert.Equal(expectedOutputfile, page.CreateOutputFile()); + } } -- GitLab From b02c9617b0a2e818de99aff993c0d9f263bcc3a1 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Tue, 11 Jul 2023 17:28:31 -0300 Subject: [PATCH 5/9] reafactor: properties reorganized --- source/Models/FrontMatter.cs | 22 +++++++------ source/Models/IFrontMatter.cs | 61 +++++++++++++++++++++++++---------- source/Models/Page.cs | 52 ++++++++++++++--------------- 3 files changed, 82 insertions(+), 53 deletions(-) diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs index 2ca8bc4..5f42379 100644 --- a/source/Models/FrontMatter.cs +++ b/source/Models/FrontMatter.cs @@ -16,12 +16,6 @@ public class FrontMatter : IFrontMatter, IParams /// public string? Title { get; set; } = string.Empty; - /// - public string? Section { get; set; } = string.Empty; - - /// - public Kind Kind { get; set; } = Kind.single; - /// public string? Type { get; set; } = "page"; @@ -29,7 +23,10 @@ public class FrontMatter : IFrontMatter, IParams public string? URL { get; init; } /// - public string RawContent { get; set; } = string.Empty; + public List? Aliases { get; set; } + + /// + public string? Section { get; set; } = string.Empty; /// public DateTime? Date { get; set; } @@ -44,13 +41,18 @@ public class FrontMatter : IFrontMatter, IParams public DateTime? ExpiryDate { get; set; } /// - public List? Aliases { get; set; } - + public int Weight { get; set; } = 0; + /// public List? Tags { get; set; } /// - public int Weight { get; set; } = 0; + [YamlIgnore] + public string RawContent { get; set; } = string.Empty; + + /// + [YamlIgnore] + public Kind Kind { get; set; } = Kind.single; /// [YamlIgnore] diff --git a/source/Models/IFrontMatter.cs b/source/Models/IFrontMatter.cs index ac91dbf..68e1da4 100644 --- a/source/Models/IFrontMatter.cs +++ b/source/Models/IFrontMatter.cs @@ -14,7 +14,7 @@ public interface IFrontMatter : IParams public string? Title { get; } /// - /// The directory where the content is located. + /// The first directory where the content is located, inside content. /// /// /// @@ -24,59 +24,86 @@ public interface IFrontMatter : IParams string? Section { get; } /// - /// The type of content. It's the same of the Section, if not specified. + /// The type of content. It's the will be "page", if not specified. /// string? Type { get; } /// /// The URL pattern to be used to create the url. + /// Liquid template can be used to use tokens. /// + /// + /// + /// + /// URL: my-page + /// + /// will be converted to /my-page, independetly of the page title. + /// + /// + /// + /// URL: "{{ page.Parent.Title }}/{{ page.Title }}" + /// + /// will try to convert page.Parent.Title and page.Title. + /// string? URL { get; } /// - /// The type of the page, if it's a single page, a list of pages or the home page. - /// - Kind Kind { get; } - - /// - /// Gets or sets the date of the page. + /// Date of the post. Will be used as the if it's not set. + /// Unless the option is set to true, + /// the dates set from the future will be ignored. /// DateTime? Date { get; } /// - /// Gets or sets the last modification date of the page. + /// Last modification date of the page. + /// Useful to notify users that the content was updated. /// DateTime? LastMod { get; } /// - /// Gets or sets the publish date of the page. + /// Publish date of the page. If not set, the will be used instead. + /// Unless the option is set to true, + /// the dates set from the future will be ignored. /// DateTime? PublishDate { get; } /// - /// Gets or sets the expiry date of the page. + /// Expiry date of the page. /// DateTime? ExpiryDate { get; } /// - /// Secondary URL patterns to be used to create the url. + /// 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; } + /// + /// Page weight. Used for sorting by default. + /// + int Weight { get; } + /// /// A list of tags, if any. /// public List? Tags { get; } /// - /// Page weight. Useful for sorting. + /// Raw content from the Markdown file, bellow the front matter. /// - int Weight { get; } + string RawContent { get; } /// - /// Raw content, from the Markdown file. + /// The kind of the page, if it's a single page, a list of pages or the home page. + /// It's used to determine the proper theme file. /// - string RawContent { get; } + Kind Kind { get; } + + /// + /// The source filename, without the extension. ;) + /// + public string? SourcePath { get; } /// /// The source filename, without the extension. ;) @@ -84,7 +111,7 @@ public interface IFrontMatter : IParams string? SourceFileNameWithoutExtension { get; } /// - /// The source directory of the file. + /// The source directory of the file, without the file name. /// string? SourcePathDirectory { get; } diff --git a/source/Models/Page.cs b/source/Models/Page.cs index f9d1789..e674b11 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -23,60 +23,60 @@ public class Page : IFrontMatter public string? Title => frontMatter.Title; /// - public string? Section => frontMatter.Section; + public string? Type => frontMatter.Type; /// - public Kind Kind - { - get => frontMatter.Kind; - set => frontMatter.Kind = value; - } + public string? URL => frontMatter.URL; /// - public string? Type => frontMatter.Type; + public List? Aliases => frontMatter.Aliases; /// - public string? URL => frontMatter.URL; + public string? Section => frontMatter.Section; /// - public string RawContent => frontMatter.RawContent; + public DateTime? Date => frontMatter.Date; /// - public string? SourceFileNameWithoutExtension => frontMatter.SourceFileNameWithoutExtension; + public DateTime? LastMod => frontMatter.LastMod; /// - public string? SourcePathDirectory => frontMatter.SourcePathDirectory; + public DateTime? PublishDate => frontMatter.PublishDate; /// - public string? SourcePath => frontMatter.SourcePath; + public DateTime? ExpiryDate => frontMatter.ExpiryDate; /// - public Dictionary Params - { - get => frontMatter.Params; - set => frontMatter.Params = value; - } + public int Weight => frontMatter.Weight; /// - public DateTime? Date => frontMatter.Date; + public List? Tags => frontMatter.Tags; /// - public DateTime? LastMod => frontMatter.LastMod; + public string RawContent => frontMatter.RawContent; /// - public DateTime? PublishDate => frontMatter.PublishDate; + public Kind Kind + { + get => frontMatter.Kind; + set => frontMatter.Kind = value; + } /// - public DateTime? ExpiryDate => frontMatter.ExpiryDate; + public string? SourcePath => frontMatter.SourcePath; /// - public List? Aliases => frontMatter.Aliases; + public string? SourceFileNameWithoutExtension => frontMatter.SourceFileNameWithoutExtension; /// - public int Weight => frontMatter.Weight; + public string? SourcePathDirectory => frontMatter.SourcePathDirectory; /// - public List? Tags => frontMatter.Tags; + public Dictionary Params + { + get => frontMatter.Params; + set => frontMatter.Params = value; + } #endregion IFrontMatter @@ -84,8 +84,8 @@ public class Page : IFrontMatter /// The source directory of the file. /// [YamlIgnore] - public string? SourcePathLastDirectory => string.IsNullOrEmpty(SourcePathDirectory) - ? null + public string? SourcePathLastDirectory => string.IsNullOrEmpty(SourcePathDirectory) + ? null : Path.GetFileName(Path.GetFullPath(SourcePathDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); -- GitLab From 0d8c1b6df3a24ae368a557b3544e46edfa8afae1 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Tue, 11 Jul 2023 21:02:35 -0300 Subject: [PATCH 6/9] refactor: SiteSettings and BaseOptions --- source/BaseGeneratorCommand.cs | 1 + source/BuildCommand.cs | 3 +- source/BuildOptions.cs | 16 --- source/Helpers/FileUtils.cs | 10 +- source/Helpers/SiteHelper.cs | 22 ++-- source/Helpers/Urlizer.cs | 1 + .../CommandLineOptions/BaseOptions.cs} | 6 +- .../Models/CommandLineOptions/BuildOptions.cs | 8 ++ .../CommandLineOptions}/IGenerateOptions.cs | 2 +- .../Models/CommandLineOptions/ServeOptions.cs | 8 ++ source/Models/Page.cs | 92 +++----------- source/Models/Site.cs | 116 ++++++++--------- source/Models/SiteCacheManager.cs | 34 +++++ source/Models/SiteSettings.cs | 32 +++++ source/Parser/IFrontmatterParser.cs | 2 +- source/Parser/YAMLParser.cs | 6 +- source/Program.cs | 1 + source/ServeCommand.cs | 3 +- test/BaseGeneratorCommandTests.cs | 1 + test/Models/PageTests.cs | 13 +- test/Models/SiteTests.cs | 118 +++++++++++------- test/Parser/YAMLParserTests.cs | 38 +++--- 22 files changed, 285 insertions(+), 248 deletions(-) delete mode 100644 source/BuildOptions.cs rename source/{ServeOptions.cs => Models/CommandLineOptions/BaseOptions.cs} (59%) create mode 100644 source/Models/CommandLineOptions/BuildOptions.cs rename source/{ => Models/CommandLineOptions}/IGenerateOptions.cs (90%) create mode 100644 source/Models/CommandLineOptions/ServeOptions.cs create mode 100644 source/Models/SiteCacheManager.cs create mode 100644 source/Models/SiteSettings.cs diff --git a/source/BaseGeneratorCommand.cs b/source/BaseGeneratorCommand.cs index bc83f34..5428be0 100644 --- a/source/BaseGeneratorCommand.cs +++ b/source/BaseGeneratorCommand.cs @@ -6,6 +6,7 @@ using Fluid.Values; using Serilog; using SuCoS.Helpers; using SuCoS.Models; +using SuCoS.Models.CommandLineOptions; using SuCoS.Parser; namespace SuCoS; diff --git a/source/BuildCommand.cs b/source/BuildCommand.cs index 01b21ad..6f28201 100644 --- a/source/BuildCommand.cs +++ b/source/BuildCommand.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Serilog; +using SuCoS.Models.CommandLineOptions; namespace SuCoS; @@ -48,7 +49,7 @@ public class BuildCommand : BaseGeneratorCommand _ = Parallel.ForEach(site.PagesReferences, pair => { var (url, page) = pair; - var result = page.CreateOutputFile(); + var result = page.CompleteContent; var path = (url + (site.UglyURLs ? "" : "/index.html")).TrimStart('/'); diff --git a/source/BuildOptions.cs b/source/BuildOptions.cs deleted file mode 100644 index fc27214..0000000 --- a/source/BuildOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace SuCoS; - -/// -/// Command line options for the build command. -/// -public class BuildOptions : IGenerateOptions -{ - /// - public string Source { get; set; } = "."; - - /// - public string? Output { get; init; } - - /// - public bool Future { get; set; } -} diff --git a/source/Helpers/FileUtils.cs b/source/Helpers/FileUtils.cs index ec7c9cd..25c490e 100644 --- a/source/Helpers/FileUtils.cs +++ b/source/Helpers/FileUtils.cs @@ -16,23 +16,23 @@ public static class FileUtils ///
/// The theme path. /// The page to determine the template index. - /// Site data. + /// Site data. /// Indicates whether the template is a base template. /// The content of the template file. - public static string GetTemplate(string themePath, Page page, Site site, bool isBaseTemplate = false) + public static string GetTemplate(string themePath, Page page, SiteCacheManager cacheManager, bool isBaseTemplate = false) { if (page is null) { throw new ArgumentNullException(nameof(page)); } - if (site is null) + if (cacheManager is null) { - throw new ArgumentNullException(nameof(site)); + throw new ArgumentNullException(nameof(cacheManager)); } var index = (page.Section, page.Kind, page.Type); - var cache = isBaseTemplate ? site.baseTemplateCache : site.contentTemplateCache; + var cache = isBaseTemplate ? cacheManager.baseTemplateCache : cacheManager.contentTemplateCache; // Check if the template content is already cached if (cache.TryGetValue(index, out var content)) diff --git a/source/Helpers/SiteHelper.cs b/source/Helpers/SiteHelper.cs index e391105..4ebcc4f 100644 --- a/source/Helpers/SiteHelper.cs +++ b/source/Helpers/SiteHelper.cs @@ -4,6 +4,7 @@ using Fluid; using Microsoft.Extensions.FileProviders; using Serilog; using SuCoS.Models; +using SuCoS.Models.CommandLineOptions; using SuCoS.Parser; namespace SuCoS.Helpers; @@ -24,16 +25,22 @@ public static class SiteHelper throw new ArgumentNullException(nameof(stopwatch)); } - Site site; + SiteSettings siteSettings; try { - site = ParseSettings(configFile, options, frontMatterParser, whereParamsFilter, logger); + siteSettings = ParseSettings(configFile, options, frontMatterParser, whereParamsFilter, logger); } catch { throw new FormatException("Error reading app config"); } + var site = new Site(options, siteSettings, logger, null); + + // Liquid template options, needed to theme the content + // but also parse URLs + site.TemplateOptions.Filters.AddFilter("whereParams", whereParamsFilter); + site.ResetCache(); stopwatch.Start("Parse"); @@ -83,7 +90,7 @@ public static class SiteHelper /// The method to be used in the whereParams. /// The logger instance. Injectable for testing /// The site settings. - private static Site ParseSettings(string configFile, IGenerateOptions options, IFrontMatterParser frontMatterParser, FilterDelegate whereParamsFilter, ILogger logger) + private static SiteSettings ParseSettings(string configFile, IGenerateOptions options, IFrontMatterParser frontMatterParser, FilterDelegate whereParamsFilter, ILogger logger) { if (options is null) { @@ -106,15 +113,6 @@ public static class SiteHelper var fileContent = File.ReadAllText(filePath); var site = frontMatterParser.ParseSiteSettings(fileContent); - site.Logger = logger; - site.options = options; - site.SourceDirectoryPath = options.Source; - site.OutputPath = options.Output!; - - // Liquid template options, needed to theme the content - // but also parse URLs - site.TemplateOptions.Filters.AddFilter("whereParams", whereParamsFilter); - if (site is null) { throw new FormatException("Error reading app config"); diff --git a/source/Helpers/Urlizer.cs b/source/Helpers/Urlizer.cs index 1abc0ac..0b83bfd 100644 --- a/source/Helpers/Urlizer.cs +++ b/source/Helpers/Urlizer.cs @@ -12,6 +12,7 @@ public static partial class Urlizer { [GeneratedRegex(@"[^a-zA-Z0-9]+")] private static partial Regex UrlizeRegexAlpha(); + [GeneratedRegex(@"[^a-zA-Z0-9.]+")] private static partial Regex UrlizeRegexAlphaDot(); diff --git a/source/ServeOptions.cs b/source/Models/CommandLineOptions/BaseOptions.cs similarity index 59% rename from source/ServeOptions.cs rename to source/Models/CommandLineOptions/BaseOptions.cs index 86169f7..5b20dc6 100644 --- a/source/ServeOptions.cs +++ b/source/Models/CommandLineOptions/BaseOptions.cs @@ -1,9 +1,9 @@ -namespace SuCoS; +namespace SuCoS.Models.CommandLineOptions; /// -/// Command line options for the serve command. +/// Basic Command line options for the serve and build command. /// -public class ServeOptions : IGenerateOptions +public class BaseOptions : IGenerateOptions { /// public string Source { get; set; } = "."; diff --git a/source/Models/CommandLineOptions/BuildOptions.cs b/source/Models/CommandLineOptions/BuildOptions.cs new file mode 100644 index 0000000..738492e --- /dev/null +++ b/source/Models/CommandLineOptions/BuildOptions.cs @@ -0,0 +1,8 @@ +namespace SuCoS.Models.CommandLineOptions; + +/// +/// Command line options for the build command. +/// +public class BuildOptions : BaseOptions +{ +} diff --git a/source/IGenerateOptions.cs b/source/Models/CommandLineOptions/IGenerateOptions.cs similarity index 90% rename from source/IGenerateOptions.cs rename to source/Models/CommandLineOptions/IGenerateOptions.cs index 585b749..aae90e7 100644 --- a/source/IGenerateOptions.cs +++ b/source/Models/CommandLineOptions/IGenerateOptions.cs @@ -1,4 +1,4 @@ -namespace SuCoS; +namespace SuCoS.Models.CommandLineOptions; /// /// Command line options for the build and serve command. diff --git a/source/Models/CommandLineOptions/ServeOptions.cs b/source/Models/CommandLineOptions/ServeOptions.cs new file mode 100644 index 0000000..2aace6b --- /dev/null +++ b/source/Models/CommandLineOptions/ServeOptions.cs @@ -0,0 +1,8 @@ +namespace SuCoS.Models.CommandLineOptions; + +/// +/// Command line options for the serve command. +/// +public class ServeOptions : BaseOptions +{ +} diff --git a/source/Models/Page.cs b/source/Models/Page.cs index e674b11..0205f01 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -6,7 +6,6 @@ using System.Linq; using Fluid; using Markdig; using SuCoS.Helpers; -using YamlDotNet.Serialization; namespace SuCoS.Models; @@ -83,7 +82,6 @@ public class Page : IFrontMatter /// /// The source directory of the file. /// - [YamlIgnore] public string? SourcePathLastDirectory => string.IsNullOrEmpty(SourcePathDirectory) ? null : Path.GetFileName(Path.GetFullPath(SourcePathDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); @@ -92,69 +90,58 @@ public class Page : IFrontMatter /// /// Point to the site configuration. /// - [YamlIgnore] public Site Site { get; set; } /// /// Secondary URL patterns to be used to create the url. /// - [YamlIgnore] public List? AliasesProcessed { get; set; } /// /// The URL for the content. /// - [YamlIgnore] public string? Permalink { get; set; } /// /// Other content that mention this content. /// Used to create the tags list and Related Posts section. /// - [YamlIgnore] public ConcurrentBag? PagesReferences { get; set; } /// /// Other content that mention this content. /// Used to create the tags list and Related Posts section. /// - [YamlIgnore] public Page? Parent { get; set; } /// /// Plain markdown content, without HTML. /// - [YamlIgnore] public string Plain => Markdown.ToPlainText(RawContent, Site.MarkdownPipeline); /// /// A list of tags, if any. /// - [YamlIgnore] public ConcurrentBag? TagsReference { get; set; } /// /// Just a simple check if the current page is the home page /// - [YamlIgnore] public bool IsHome => Site.Home == this; /// /// Just a simple check if the current page is a section page /// - [YamlIgnore] public bool IsSection => Type == "section"; /// /// Just a simple check if the current page is a "page" /// - [YamlIgnore] public bool IsPage => Kind == Kind.single; /// /// The number of words in the main content /// - [YamlIgnore] public int WordCount => Plain.Split(nonWords, StringSplitOptions.RemoveEmptyEntries).Length; private static readonly char[] nonWords = new[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r' }; @@ -162,14 +149,7 @@ public class Page : IFrontMatter /// /// The markdown content converted to HTML /// - public string ContentPreRendered - { - get - { - contentPreRenderedCached ??= Markdown.ToHtml(RawContent, Site.MarkdownPipeline); - return contentPreRenderedCached; - } - } + public string ContentPreRendered => contentPreRenderedCached.Value; /// /// The processed content. @@ -178,16 +158,18 @@ public class Page : IFrontMatter { get { - if (contentCacheTime is not null && !(Site.IgnoreCacheBefore > contentCacheTime)) - { - return contentCache!; - } - contentCache = CreateContent(); - contentCacheTime = clock.UtcNow; + contentCache = ParseAndRenderTemplate(false, "Error rendering theme template: {Error}"); return contentCache!; } } + /// + /// Creates the output file by applying the theme templates to the page content. + /// + /// The processed output file content. + public string CompleteContent => ParseAndRenderTemplate(true, "Error parsing theme template: {Error}"); + + /// /// Other content that mention this content. /// Used to create the tags list and Related Posts section. @@ -255,18 +237,13 @@ public class Page : IFrontMatter /// /// The markdown content. /// - private string? contentPreRenderedCached { get; set; } + private Lazy contentPreRenderedCached => new(() => Markdown.ToHtml(RawContent, Site.MarkdownPipeline)); /// /// The cached content. /// private string? contentCache { get; set; } - /// - /// The time when the content was cached. - /// - private DateTime? contentCacheTime { get; set; } - private const string urlForIndex = @"{%- liquid if page.Parent echo page.Parent.Permalink @@ -294,8 +271,6 @@ endif private List? pagesCached { get; set; } - private ISystemClock clock => Site.Clock; - /// /// Required. /// @@ -355,51 +330,17 @@ endif if (!Path.IsPathRooted(permaLink) && !permaLink.StartsWith('/')) { - permaLink = '/' + permaLink; + permaLink = $"/{permaLink}"; } return Urlizer.UrlizePath(permaLink); } - - /// - /// Creates the output file by applying the theme templates to the page content. - /// - /// The processed output file content. - public string CreateOutputFile() - { - // Process the theme base template - // If the theme base template file is available, parse and render the template using the page meta data - // Otherwise, use the processed content as the final result - // Any error during parsing is logged, and an empty string is returned - // The final result is stored in the 'result' variable and returned - var fileContents = FileUtils.GetTemplate(Site.SourceThemePath, this, Site, true); - if (string.IsNullOrEmpty(fileContents)) - { - return Content; - } - - if (Site.FluidParser.TryParse(fileContents, out var template, out var error)) - { - var context = new TemplateContext(Site.TemplateOptions); - _ = context.SetValue("page", this); - return template.Render(context); - } - - Site.Logger?.Error("Error parsing theme template: {Error}", error); - return string.Empty; - } - - /// - /// Create the page content, with converted Markdown and themed. - /// - /// - private string CreateContent() + private string ParseAndRenderTemplate(bool isBaseTemplate, string errorMessage) { - var fileContents = FileUtils.GetTemplate(Site.SourceThemePath, this, Site); - // Theme content + var fileContents = FileUtils.GetTemplate(Site.SourceThemePath, this, Site.CacheManager, isBaseTemplate); if (string.IsNullOrEmpty(fileContents)) { - return ContentPreRendered; + return isBaseTemplate ? Content : ContentPreRendered; } if (Site.FluidParser.TryParse(fileContents, out var template, out var error)) @@ -413,13 +354,12 @@ endif } catch (Exception ex) { - Site.Logger?.Error(ex, "Error rendering theme template: {Error}", error); + Site.Logger?.Error(ex, errorMessage, error); return string.Empty; } } - Site.Logger?.Error("Error parsing theme template: {Error}", error); + Site.Logger.Error(errorMessage, error); return string.Empty; - } } diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 888f470..907d19b 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -8,8 +8,8 @@ using Fluid; using Markdig; using Serilog; using SuCoS.Helpers; +using SuCoS.Models.CommandLineOptions; using SuCoS.Parser; -using YamlDotNet.Serialization; namespace SuCoS.Models; @@ -21,31 +21,42 @@ public class Site : IParams #region IParams /// - [YamlIgnore] - public Dictionary Params { get; set; } = new(); + /// + public Dictionary Params + { + get => settings.Params; + set => settings.Params = value; + } #endregion IParams /// - /// Site Title. + /// Command line options /// - public string Title { get; set; } = string.Empty; + public IGenerateOptions Options; /// - /// The base URL that will be used to build internal links. + /// The path where the generated site files will be saved. /// - public string BaseUrl { get; set; } = "./"; + public string OutputPath => Options.Output; - /// - /// The appearance of a URL is either ugly or pretty. - /// - public bool UglyURLs { get; set; } = false; + #region SiteSettings + + /// + public string Title => settings.Title; + + /// + public string BaseUrl => settings.BaseUrl; + + /// + public bool UglyURLs => settings.UglyURLs; + + #endregion SiteSettings /// /// The base path of the source site files. /// - [YamlIgnore] - public string SourceDirectoryPath { get; set; } = "./"; + public string SourceDirectoryPath => Options.Source; /// /// The path of the content, based on the source path. @@ -67,12 +78,6 @@ public class Site : IParams /// public string SourceThemeStaticPath => Path.Combine(SourceThemePath, "static"); - /// - /// The path where the generated site files will be saved. - /// - [YamlIgnore] - public string OutputPath { get; set; } = "./"; - /// /// List of all pages, including generated. /// @@ -124,19 +129,9 @@ public class Site : IParams public Page? Home { get; private set; } /// - /// Command line options + /// Manage all caching lists for the site /// - public IGenerateOptions? options; - - /// - /// Cache for content templates. - /// - public readonly Dictionary<(string?, Kind?, string?), string> contentTemplateCache = new(); - - /// - /// Cache for base templates. - /// - public readonly Dictionary<(string?, Kind?, string?), string> baseTemplateCache = new(); + public SiteCacheManager CacheManager = new(); /// /// The Fluid parser instance. @@ -151,7 +146,7 @@ public class Site : IParams /// /// The logger instance. /// - public ILogger? Logger; + public ILogger Logger; /// /// The time that the older cache should be ignored. @@ -164,9 +159,13 @@ public class Site : IParams public readonly ISystemClock Clock; /// - /// Cache for tag page. + /// Number of files parsed, used in the report. /// - private readonly Dictionary automaticContentCache = new(); + public int filesParsedToReport; + + private const string indexFileConst = "index.md"; + + private const string indexFileUpperConst = "INDEX.MD"; /// /// The synchronization lock object. @@ -187,49 +186,40 @@ public class Site : IParams private List? regularPagesCache; - /// - /// Number of files parsed, used in the report. - /// - public int filesParsedToReport; - - /// - /// Markdig 20+ built-in extensions - /// - /// https://github.com/xoofx/markdig - public readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder() - .UseAdvancedExtensions() - .Build(); + private readonly SiteSettings settings; /// /// Constructor /// - public Site() : this(new SystemClock()) + public Site(IGenerateOptions options, SiteSettings settings, ILogger logger, ISystemClock? clock) { - } + Options = options; + this.settings = settings; + Logger = logger; - /// - /// Constructor - /// - public Site(ISystemClock clock) - { // Liquid template options, needed to theme the content // but also parse URLs TemplateOptions.MemberAccessStrategy.Register(); TemplateOptions.MemberAccessStrategy.Register(); - Clock = clock; + Clock = clock ?? new SystemClock(); } + /// + /// Markdig 20+ built-in extensions + /// + /// https://github.com/xoofx/markdig + public readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + /// /// Resets the template cache to force a reload of all templates. /// public void ResetCache() { - baseTemplateCache.Clear(); - contentTemplateCache.Clear(); - automaticContentCache.Clear(); + CacheManager.ResetCache(); PagesReferences.Clear(); - IgnoreCacheBefore = DateTime.Now; } /// @@ -246,7 +236,7 @@ public class Site : IParams var markdownFiles = Directory.GetFiles(directory, "*.md"); - var indexPath = markdownFiles.FirstOrDefault(file => Path.GetFileName(file).ToUpperInvariant() == "INDEX.MD"); + var indexPath = markdownFiles.FirstOrDefault(file => Path.GetFileName(file).ToUpperInvariant() == indexFileUpperConst); if (indexPath != null) { markdownFiles = markdownFiles.Where(file => file != indexPath).ToArray(); @@ -295,7 +285,7 @@ public class Site : IParams var frontMatter = frontMatterParser.ParseFrontmatterAndMarkdownFromFile(filePath, SourceContentPath) ?? throw new FormatException($"Error parsing front matter for {filePath}"); - if (IsValidDate(frontMatter, options)) + if (IsValidDate(frontMatter, Options)) { page = new(frontMatter, this); PostProcessPage(page, parent, true); @@ -341,7 +331,7 @@ public class Site : IParams Page? page; lock (syncLock) { - if (!automaticContentCache.TryGetValue(key: id, out page)) + if (!CacheManager.automaticContentCache.TryGetValue(key: id, out page)) { Page? parent = null; @@ -353,7 +343,7 @@ public class Site : IParams } page = new(frontMatter, this); - automaticContentCache.Add(id, page); + CacheManager.automaticContentCache.Add(id, page); PostProcessPage(page, parent); } } @@ -385,7 +375,7 @@ public class Site : IParams Page page = new(new() { Title = Title, - SourcePath = Path.Combine(relativePath, "index.md"), + SourcePath = Path.Combine(relativePath, indexFileConst), Kind = string.IsNullOrEmpty(relativePath) ? Kind.index : Kind.list, Section = (string.IsNullOrEmpty(relativePath) ? Kind.index : Kind.list).ToString(), URL = "/", diff --git a/source/Models/SiteCacheManager.cs b/source/Models/SiteCacheManager.cs new file mode 100644 index 0000000..137dd62 --- /dev/null +++ b/source/Models/SiteCacheManager.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace SuCoS.Models; + +/// +/// Manages all the lists and dictionaries used for cache for the site +/// +public class SiteCacheManager +{ + /// + /// Cache for content templates. + /// + public readonly Dictionary<(string?, Kind?, string?), string> contentTemplateCache = new(); + + /// + /// Cache for base templates. + /// + public readonly Dictionary<(string?, Kind?, string?), string> baseTemplateCache = new(); + + /// + /// Cache for tag page. + /// + public readonly Dictionary automaticContentCache = new(); + + /// + /// Resets the template cache to force a reload of all templates. + /// + public void ResetCache() + { + baseTemplateCache.Clear(); + contentTemplateCache.Clear(); + automaticContentCache.Clear(); + } +} \ No newline at end of file diff --git a/source/Models/SiteSettings.cs b/source/Models/SiteSettings.cs new file mode 100644 index 0000000..4de5802 --- /dev/null +++ b/source/Models/SiteSettings.cs @@ -0,0 +1,32 @@ + +using System.Collections.Generic; + +namespace SuCoS.Models; + +/// +/// The main configuration of the program, extracted from the app.yaml file. +/// +public class SiteSettings : IParams +{ + /// + /// Site Title. + /// + public string Title { get; set; } = string.Empty; + + /// + /// The base URL that will be used to build internal links. + /// + public string BaseUrl { get; set; } = "./"; + + /// + /// The appearance of a URL is either ugly or pretty. + /// + public bool UglyURLs { get; set; } = false; + + #region IParams + + /// + public Dictionary Params { get; set; } = new(); + + #endregion IParams +} \ No newline at end of file diff --git a/source/Parser/IFrontmatterParser.cs b/source/Parser/IFrontmatterParser.cs index b2d967c..1318dfb 100644 --- a/source/Parser/IFrontmatterParser.cs +++ b/source/Parser/IFrontmatterParser.cs @@ -28,5 +28,5 @@ public interface IFrontMatterParser /// /// /// - Site ParseSiteSettings(string configFileContent); + SiteSettings ParseSiteSettings(string configFileContent); } \ No newline at end of file diff --git a/source/Parser/YAMLParser.cs b/source/Parser/YAMLParser.cs index ddc9380..9804569 100644 --- a/source/Parser/YAMLParser.cs +++ b/source/Parser/YAMLParser.cs @@ -99,10 +99,10 @@ public class YAMLParser : IFrontMatterParser } /// - public Site ParseSiteSettings(string yaml) + public SiteSettings ParseSiteSettings(string yaml) { - var settings = yamlDeserializerRigid.Deserialize(yaml); - ParseParams(settings, typeof(Site), yaml); + var settings = yamlDeserializerRigid.Deserialize(yaml); + ParseParams(settings, typeof(SiteSettings), yaml); return settings; } diff --git a/source/Program.cs b/source/Program.cs index 87a217a..24929ac 100644 --- a/source/Program.cs +++ b/source/Program.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Threading.Tasks; using Serilog; using Serilog.Events; +using SuCoS.Models.CommandLineOptions; namespace SuCoS; diff --git a/source/ServeCommand.cs b/source/ServeCommand.cs index a820486..403f860 100644 --- a/source/ServeCommand.cs +++ b/source/ServeCommand.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.StaticFiles; using Serilog; using SuCoS.Helpers; using SuCoS.Models; +using SuCoS.Models.CommandLineOptions; namespace SuCoS; @@ -260,7 +261,7 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable private async Task HandleRegisteredPageRequest(HttpContext context, Page page) { - var content = page.CreateOutputFile(); + var content = page.CompleteContent; content = InjectReloadScript(content); await context.Response.WriteAsync(content); } diff --git a/test/BaseGeneratorCommandTests.cs b/test/BaseGeneratorCommandTests.cs index 0b86369..02af054 100644 --- a/test/BaseGeneratorCommandTests.cs +++ b/test/BaseGeneratorCommandTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using Serilog; using SuCoS; +using SuCoS.Models.CommandLineOptions; using Xunit; namespace Test; diff --git a/test/Models/PageTests.cs b/test/Models/PageTests.cs index 3fc2a02..710d24f 100644 --- a/test/Models/PageTests.cs +++ b/test/Models/PageTests.cs @@ -3,12 +3,16 @@ using Moq; using SuCoS; using SuCoS.Models; using Xunit; +using Serilog; +using SuCoS.Models.CommandLineOptions; namespace Test.Models; public class PageTests { - private readonly ISystemClock clock; + private readonly Mock generateOptionsMock = new(); + private readonly Mock siteSettingsMock = new(); + private readonly Mock loggerMock = new(); private readonly Mock systemClockMock = new(); private readonly Site site; private const string titleCONST = "Test Title"; @@ -40,8 +44,7 @@ word03 word04 word05 6 7 eight { var testDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); systemClockMock.Setup(c => c.Now).Returns(testDate); - clock = systemClockMock.Object; - site = new(clock); + site = new Site(generateOptionsMock.Object, siteSettingsMock.Object, loggerMock.Object, systemClockMock.Object); } [Theory] @@ -114,7 +117,7 @@ word03 word04 word05 6 7 eight { var page = new Page(new(titleCONST, sourcePathCONST) { - ExpiryDate = clock.Now.AddDays(days) + ExpiryDate = systemClockMock.Object.Now.AddDays(days) }, site); // Assert @@ -146,7 +149,7 @@ word03 word04 word05 6 7 eight { var page = new Page(new(titleCONST, sourcePathCONST) { - Date = clock.Now.AddDays(1) + Date = systemClockMock.Object.Now.AddDays(1) }, site); // Act diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs index b4522b4..79c7248 100644 --- a/test/Models/SiteTests.cs +++ b/test/Models/SiteTests.cs @@ -2,6 +2,8 @@ using Xunit; using Moq; using System.Globalization; using SuCoS.Models; +using Serilog; +using SuCoS.Models.CommandLineOptions; namespace Test.Models; @@ -11,6 +13,9 @@ namespace Test.Models; public class SiteTests { private readonly Site site; + private readonly Mock generateOptionsMock = new(); + private readonly Mock siteSettingsMock = new(); + private readonly Mock loggerMock = new(); private readonly Mock systemClockMock = new(); private const string testSitePathCONST01 = ".TestSites/01"; private const string testSitePathCONST02 = ".TestSites/02-have-index"; @@ -28,7 +33,7 @@ public class SiteTests { var testDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); systemClockMock.Setup(c => c.Now).Returns(testDate); - site = new Site(systemClockMock.Object); + site = new Site(generateOptionsMock.Object, siteSettingsMock.Object, loggerMock.Object, systemClockMock.Object); } [Theory] @@ -38,6 +43,10 @@ public class SiteTests { var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST01)); + site.Options = new BaseOptions() + { + Source = siteFullPath + }; // Act site.ParseAndScanSourceFiles(Path.Combine(siteFullPath, "content")); @@ -52,8 +61,11 @@ public class SiteTests [InlineData(testSitePathCONST02)] public void Home_ShouldReturnAHomePage(string sitePath) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(site.SourceContentPath); @@ -70,8 +82,11 @@ public class SiteTests [InlineData(testSitePathCONST03, 1)] public void Page_IsSection_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -86,8 +101,11 @@ public class SiteTests [InlineData(testSitePathCONST03, 13)] public void PagesReference_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -102,8 +120,11 @@ public class SiteTests [InlineData(testSitePathCONST03, 11)] public void Page_IsPage_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -112,28 +133,14 @@ public class SiteTests Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsPage).Count()); } - [Theory] - [InlineData("test1", Kind.index, "base", "Test Content 1")] - [InlineData("test2", Kind.single, "content", "Test Content 2")] - public void ResetCache_ShouldCachesBeEmpty(string firstKeyPart, Kind secondKeyPart, string thirdKeyPart, string value) - { - var key = (firstKeyPart, secondKeyPart, thirdKeyPart); - site.baseTemplateCache.Add(key, value); - site.contentTemplateCache.Add(key, value); - site.PagesReferences.Add("test", new Page("Test Title", "sourcePath", site)); - - site.ResetCache(); - - Assert.Empty(site.baseTemplateCache); - Assert.Empty(site.contentTemplateCache); - Assert.Empty(site.PagesReferences); - } - [Fact] public void Page_Weight_ShouldReturnTheRightOrder() { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST03)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST03)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -146,8 +153,11 @@ public class SiteTests [Fact] public void Page_Weight_ShouldReturnZeroWeight() { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST02)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST02)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -160,8 +170,11 @@ public class SiteTests [Fact] public void TagSectionPage_Pages_ShouldReturnNumberTagPages() { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -179,8 +192,11 @@ public class SiteTests [Fact] public void TagPage_Pages_ShouldReturnNumberReferences() { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -200,8 +216,11 @@ public class SiteTests [InlineData("/blog/test-content-1", "

Test Content 1

\n")] public void Page_Content_ShouldReturnNullThemeContent(string url, string expectedContent) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -231,8 +250,11 @@ public class SiteTests "SINGLE-

Test Content 1

\n")] public void Page_Content_ShouldReturnNullThemeBaseofContent(string url, string expectedContentPreRendered, string expectedContent) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST05)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST05)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -242,7 +264,7 @@ public class SiteTests Assert.NotNull(page); Assert.Equal(expectedContentPreRendered, page.ContentPreRendered); Assert.Equal(expectedContent, page.Content); - Assert.Equal(expectedContent, page.CreateOutputFile()); + Assert.Equal(expectedContent, page.CompleteContent); } [Theory] @@ -253,8 +275,11 @@ public class SiteTests [InlineData("/blog/test-content-1")] public void Page_Content_ShouldReturnThrowNullThemeBaseofContent(string url) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST07)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST07)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -263,7 +288,7 @@ public class SiteTests site.PagesReferences.TryGetValue(url, out var page); Assert.NotNull(page); Assert.Equal(string.Empty, page.Content); - Assert.Equal(string.Empty, page.CreateOutputFile()); + Assert.Equal(string.Empty, page.CompleteContent); } [Theory] @@ -289,8 +314,11 @@ public class SiteTests "BASEOF-SINGLE-

Test Content 1

\n")] public void Page_Content_ShouldReturnThemeContent(string url, string expectedContentPreRendered, string expectedContent, string expectedOutputfile) { - var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST06)); - site.SourceDirectoryPath = siteFullPath; + BaseOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST06)) + }; + site.Options = options; // Act site.ParseAndScanSourceFiles(null); @@ -300,6 +328,6 @@ public class SiteTests Assert.NotNull(page); Assert.Equal(expectedContentPreRendered, page.ContentPreRendered); Assert.Equal(expectedContent, page.Content); - Assert.Equal(expectedOutputfile, page.CreateOutputFile()); + Assert.Equal(expectedOutputfile, page.CompleteContent); } } diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs index 13bab2f..af340bd 100644 --- a/test/Parser/YAMLParserTests.cs +++ b/test/Parser/YAMLParserTests.cs @@ -4,13 +4,19 @@ using SuCoS.Parser; using System.Globalization; using SuCoS.Helpers; using SuCoS.Models; +using Serilog; +using SuCoS.Models.CommandLineOptions; namespace Test.Parser; public class YAMLParserTests { private readonly YAMLParser parser; - private readonly Mock siteDefault; + private readonly Site siteDefault; + private readonly Mock generateOptionsMock = new(); + private readonly Mock siteSettingsMock = new(); + private readonly Mock loggerMock = new(); + private readonly Mock systemClockMock = new(); private const string pageFrontmaterCONST = @"Title: Test Title Type: post @@ -50,7 +56,7 @@ NestedData: public YAMLParserTests() { parser = new YAMLParser(); - siteDefault = new Mock(); + siteDefault = new Site(generateOptionsMock.Object, siteSettingsMock.Object, loggerMock.Object, systemClockMock.Object); pageContent = @$"--- {pageFrontmaterCONST} --- @@ -153,7 +159,7 @@ Title [Fact] public void ParseParams_ShouldFillParamsWithNonMatchingFields() { - var page = new Page("Test Title", "/test.md", siteDefault.Object); + var page = new Page("Test Title", "/test.md", siteDefault); // Act parser.ParseParams(page, typeof(Page), pageFrontmaterCONST); @@ -168,10 +174,10 @@ Title { var date = DateTime.Parse("2023-07-01", CultureInfo.InvariantCulture); var frontMatter = parser.ParseFrontmatterAndMarkdown("", pageContent); - Page page = new(frontMatter, siteDefault.Object); + Page page = new(frontMatter, siteDefault); // Act - siteDefault.Object.PostProcessPage(page); + siteDefault.PostProcessPage(page); // Asset Assert.Equal(date, frontMatter.Date); @@ -182,10 +188,10 @@ Title { // Act var frontMatter = parser.ParseFrontmatterAndMarkdown("", pageContent); - Page page = new(frontMatter, siteDefault.Object); + Page page = new(frontMatter, siteDefault); // Act - siteDefault.Object.PostProcessPage(page); + siteDefault.PostProcessPage(page); // Asset Assert.Equal(2, page.TagsReference?.Count); @@ -264,24 +270,24 @@ Title [Fact] public void SiteParams_ShouldThrowExceptionWhenTypeIsNull() { - Assert.Throws(() => parser.ParseParams(siteDefault.Object, null!, siteContentCONST)); + Assert.Throws(() => parser.ParseParams(siteDefault, null!, siteContentCONST)); } [Fact] public void SiteParams_ShouldHandleEmptyContent() { - parser.ParseParams(siteDefault.Object, typeof(Site), string.Empty); - Assert.Empty(siteDefault.Object.Params); + parser.ParseParams(siteDefault, typeof(Site), string.Empty); + Assert.Empty(siteDefault.Params); } [Fact] public void SiteParams_ShouldPopulateParamsWithExtraFields() { - 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]); + parser.ParseParams(siteDefault, typeof(Site), siteContentCONST); + Assert.NotEmpty(siteDefault.Params); + Assert.True(siteDefault.Params.ContainsKey("customParam")); + Assert.Equal("Custom Value", siteDefault.Params["customParam"]); + Assert.Equal(new[] { "Test", "Real Data" }, ((Dictionary)siteDefault.Params["NestedData"])["Level2"]); + Assert.Equal("Test", ((siteDefault?.Params["NestedData"] as Dictionary)?["Level2"] as List)?[0]); } } -- GitLab From b3bdb76b3d3d8e48f2ecb5f39d98cf34e72be28f Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Tue, 11 Jul 2023 23:02:22 -0300 Subject: [PATCH 7/9] perf: optimizations in Site and Page caches --- source/BuildCommand.cs | 13 +- source/Helpers/SiteHelper.cs | 13 +- .../Models/CommandLineOptions/BuildOptions.cs | 15 +- .../{BaseOptions.cs => GenerateOptions.cs} | 5 +- .../CommandLineOptions/IGenerateOptions.cs | 5 - .../Models/CommandLineOptions/ServeOptions.cs | 2 +- source/Models/IFrontMatter.cs | 1 + source/Models/Page.cs | 24 +-- source/Models/Site.cs | 150 ++++++------------ source/Models/SiteCacheManager.cs | 4 +- source/Program.cs | 4 +- .../04-tags/content/blog/tags-01.md | 3 +- .../04-tags/content/blog/tags-02.md | 3 +- .../04-tags/content/blog/tags-03.md | 7 + .../04-tags/content/blog/tags-04.md | 7 + .../04-tags/content/blog/tags-05.md | 7 + .../04-tags/content/blog/tags-06.md | 7 + .../04-tags/content/blog/tags-07.md | 7 + .../04-tags/content/blog/tags-08.md | 7 + .../04-tags/content/blog/tags-09.md | 7 + .../04-tags/content/blog/tags-10.md | 7 + test/BaseGeneratorCommandTests.cs | 2 +- test/Models/PageTests.cs | 35 ++-- test/Models/SiteTests.cs | 41 ++--- test/Parser/YAMLParserTests.cs | 6 +- 25 files changed, 195 insertions(+), 187 deletions(-) rename source/Models/CommandLineOptions/{BaseOptions.cs => GenerateOptions.cs} (71%) create mode 100644 test/.TestSites/04-tags/content/blog/tags-03.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-04.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-05.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-06.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-07.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-08.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-09.md create mode 100644 test/.TestSites/04-tags/content/blog/tags-10.md diff --git a/source/BuildCommand.cs b/source/BuildCommand.cs index 6f28201..64db956 100644 --- a/source/BuildCommand.cs +++ b/source/BuildCommand.cs @@ -12,6 +12,8 @@ namespace SuCoS; /// public class BuildCommand : BaseGeneratorCommand { + private readonly BuildOptions options; + /// /// Entry point of the build command. It will be called by the main program /// in case the build command is invoked (which is by default). @@ -20,10 +22,7 @@ public class BuildCommand : BaseGeneratorCommand /// The logger instance. Injectable for testing public BuildCommand(BuildOptions options, ILogger logger) : base(options, logger) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + this.options = options ?? throw new ArgumentNullException(nameof(options)); logger.Information("Output path: {output}", options.Output); @@ -31,10 +30,10 @@ public class BuildCommand : BaseGeneratorCommand CreateOutputFiles(); // Copy theme static folder files into the root of the output folder - CopyFolder(site.SourceThemeStaticPath, site.OutputPath); + CopyFolder(site.SourceThemeStaticPath, options.Output); // Copy static folder files into the root of the output folder - CopyFolder(site.SourceStaticPath, site.OutputPath); + CopyFolder(site.SourceStaticPath, options.Output); // Generate the build report stopwatch.LogReport(site.Title); @@ -54,7 +53,7 @@ public class BuildCommand : BaseGeneratorCommand var path = (url + (site.UglyURLs ? "" : "/index.html")).TrimStart('/'); // Generate the output path - var outputAbsolutePath = Path.Combine(site.OutputPath, path); + var outputAbsolutePath = Path.Combine(options.Output, path); var outputDirectory = Path.GetDirectoryName(outputAbsolutePath); if (!Directory.Exists(outputDirectory)) diff --git a/source/Helpers/SiteHelper.cs b/source/Helpers/SiteHelper.cs index 4ebcc4f..4f9c57d 100644 --- a/source/Helpers/SiteHelper.cs +++ b/source/Helpers/SiteHelper.cs @@ -28,7 +28,7 @@ public static class SiteHelper SiteSettings siteSettings; try { - siteSettings = ParseSettings(configFile, options, frontMatterParser, whereParamsFilter, logger); + siteSettings = ParseSettings(configFile, options, frontMatterParser); } catch { @@ -87,10 +87,8 @@ public static class SiteHelper /// The generate options. /// The front matter parser. /// The site settings file. - /// The method to be used in the whereParams. - /// The logger instance. Injectable for testing /// The site settings. - private static SiteSettings ParseSettings(string configFile, IGenerateOptions options, IFrontMatterParser frontMatterParser, FilterDelegate whereParamsFilter, ILogger logger) + private static SiteSettings ParseSettings(string configFile, IGenerateOptions options, IFrontMatterParser frontMatterParser) { if (options is null) { @@ -111,12 +109,7 @@ public static class SiteHelper } var fileContent = File.ReadAllText(filePath); - var site = frontMatterParser.ParseSiteSettings(fileContent); - - if (site is null) - { - throw new FormatException("Error reading app config"); - } + var site = frontMatterParser.ParseSiteSettings(fileContent) ?? throw new FormatException("Error reading app config"); return site; } catch diff --git a/source/Models/CommandLineOptions/BuildOptions.cs b/source/Models/CommandLineOptions/BuildOptions.cs index 738492e..02751e7 100644 --- a/source/Models/CommandLineOptions/BuildOptions.cs +++ b/source/Models/CommandLineOptions/BuildOptions.cs @@ -3,6 +3,19 @@ namespace SuCoS.Models.CommandLineOptions; /// /// Command line options for the build command. /// -public class BuildOptions : BaseOptions +public class BuildOptions : GenerateOptions { + /// + /// The path of the output files. + /// + public string Output { get; } + + /// + /// Constructor + /// + /// + public BuildOptions(string output) : base() + { + Output = output; + } } diff --git a/source/Models/CommandLineOptions/BaseOptions.cs b/source/Models/CommandLineOptions/GenerateOptions.cs similarity index 71% rename from source/Models/CommandLineOptions/BaseOptions.cs rename to source/Models/CommandLineOptions/GenerateOptions.cs index 5b20dc6..1ed1fc2 100644 --- a/source/Models/CommandLineOptions/BaseOptions.cs +++ b/source/Models/CommandLineOptions/GenerateOptions.cs @@ -3,14 +3,11 @@ namespace SuCoS.Models.CommandLineOptions; /// /// Basic Command line options for the serve and build command. /// -public class BaseOptions : IGenerateOptions +public class GenerateOptions : IGenerateOptions { /// public string Source { get; set; } = "."; - /// - public string? Output { get; set; } - /// public bool Future { get; set; } } diff --git a/source/Models/CommandLineOptions/IGenerateOptions.cs b/source/Models/CommandLineOptions/IGenerateOptions.cs index aae90e7..472bb54 100644 --- a/source/Models/CommandLineOptions/IGenerateOptions.cs +++ b/source/Models/CommandLineOptions/IGenerateOptions.cs @@ -10,11 +10,6 @@ public interface IGenerateOptions /// string Source { get; } - /// - /// The path of the output files. - /// - public string? Output { get; } - /// /// Consider /// diff --git a/source/Models/CommandLineOptions/ServeOptions.cs b/source/Models/CommandLineOptions/ServeOptions.cs index 2aace6b..9e2b5c9 100644 --- a/source/Models/CommandLineOptions/ServeOptions.cs +++ b/source/Models/CommandLineOptions/ServeOptions.cs @@ -3,6 +3,6 @@ namespace SuCoS.Models.CommandLineOptions; /// /// Command line options for the serve command. /// -public class ServeOptions : BaseOptions +public class ServeOptions : GenerateOptions { } diff --git a/source/Models/IFrontMatter.cs b/source/Models/IFrontMatter.cs index 68e1da4..a06b570 100644 --- a/source/Models/IFrontMatter.cs +++ b/source/Models/IFrontMatter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using SuCoS.Models.CommandLineOptions; namespace SuCoS.Models; diff --git a/source/Models/Page.cs b/source/Models/Page.cs index 0205f01..73f6aad 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -106,7 +106,7 @@ public class Page : IFrontMatter /// Other content that mention this content. /// Used to create the tags list and Related Posts section. /// - public ConcurrentBag? PagesReferences { get; set; } + public ConcurrentBag PagesReferences { get; } = new(); /// /// Other content that mention this content. @@ -122,7 +122,7 @@ public class Page : IFrontMatter /// /// A list of tags, if any. /// - public ConcurrentBag? TagsReference { get; set; } + public ConcurrentBag TagsReference { get; } = new(); /// /// Just a simple check if the current page is the home page @@ -178,11 +178,6 @@ public class Page : IFrontMatter { get { - if (PagesReferences is null) - { - return new List(); - } - if (pagesCached is not null) { return pagesCached; @@ -271,21 +266,6 @@ endif private List? pagesCached { get; set; } - /// - /// Required. - /// - public Page( - string title, - string sourcePath, - Site site - ) - : this(new() - { - Title = title, - SourcePath = sourcePath, - }, site) - { } - /// /// Constructor /// diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 907d19b..93808a8 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,7 +8,6 @@ using System.Threading.Tasks; using Fluid; using Markdig; using Serilog; -using SuCoS.Helpers; using SuCoS.Models.CommandLineOptions; using SuCoS.Parser; @@ -35,11 +35,6 @@ public class Site : IParams /// public IGenerateOptions Options; - /// - /// The path where the generated site files will be saved. - /// - public string OutputPath => Options.Output; - #region SiteSettings /// @@ -53,25 +48,20 @@ public class Site : IParams #endregion SiteSettings - /// - /// The base path of the source site files. - /// - public string SourceDirectoryPath => Options.Source; - /// /// The path of the content, based on the source path. /// - public string SourceContentPath => Path.Combine(SourceDirectoryPath, "content"); + public string SourceContentPath => Path.Combine(Options.Source, "content"); /// /// The path of the static content (that will be copied as is), based on the source path. /// - public string SourceStaticPath => Path.Combine(SourceDirectoryPath, "static"); + public string SourceStaticPath => Path.Combine(Options.Source, "static"); /// /// The path theme. /// - public string SourceThemePath => Path.Combine(SourceDirectoryPath, "theme"); + public string SourceThemePath => Path.Combine(Options.Source, "theme"); /// /// The path of the static content (that will be copied as is), based on the theme path. @@ -105,7 +95,7 @@ public class Site : IParams /// /// List of all pages, including generated, by their permalink. /// - public Dictionary PagesReferences { get; } = new(); + public ConcurrentDictionary PagesReferences { get; } = new(); /// /// List of pages from the content folder. @@ -167,11 +157,6 @@ public class Site : IParams private const string indexFileUpperConst = "INDEX.MD"; - /// - /// The synchronization lock object. - /// - private readonly object syncLock = new(); - /// /// The synchronization lock object during ProstProcess. /// @@ -243,11 +228,11 @@ public class Site : IParams var page = ParseSourceFile(parent, indexPath); if (level == 0) { - PagesReferences.Remove(page!.Permalink!); + PagesReferences.TryRemove(page!.Permalink!, out var _); Home = page; page!.Permalink = "/"; page.Kind = Kind.index; - PagesReferences.Add(page.Permalink, page); + PagesReferences.GetOrAdd(page.Permalink, page); } else { @@ -257,12 +242,12 @@ public class Site : IParams else if (level == 0) { // TODO: unify the with section creation process - Home = CreateIndexPage(string.Empty); + Home = CreateSystemPage(string.Empty, Title); } else if (level == 1) { var section = new DirectoryInfo(directory).Name; - parent = CreateSectionPage(section); + parent = CreateSystemPage(section, section); } _ = Parallel.ForEach(markdownFiles, filePath => @@ -302,90 +287,68 @@ public class Site : IParams return page; } - private Page CreateSectionPage(string section) - { - var contentTemplate = new FrontMatter() - { - Title = section, - Section = "section", - Kind = Kind.list, - Type = "section", - URL = section, - SourcePath = section - }; - return CreateSystemPage(contentTemplate, null); - } - /// - /// Create a page not from the content folder, but as part of the process. - /// It's used to create tag pages, section list pages, etc. + /// Creates the page for the site index. /// - public Page CreateSystemPage(FrontMatter frontMatter, Page? originalPage) + /// The relative path of the page. + /// + /// + /// + /// The created page for the index. + private Page CreateSystemPage(string relativePath, string title, string? sectionName = null, Page? originalPage = null) { - if (frontMatter is null || string.IsNullOrEmpty(frontMatter.URL)) + sectionName ??= "section"; + var isIndex = string.IsNullOrEmpty(relativePath); + FrontMatter frontMatter = new() { - throw new ArgumentNullException(nameof(frontMatter)); - } + Kind = isIndex ? Kind.index : Kind.list, + Section = isIndex ? "index" : sectionName, + SourcePath = Path.Combine(relativePath, indexFileConst), + Title = title, + Type = isIndex ? "index" : sectionName, + URL = relativePath, + }; var id = frontMatter.URL; - Page? page; - lock (syncLock) + + // Get or create the page + var lazyPage = CacheManager.automaticContentCache.GetOrAdd(id, new Lazy(() => { - if (!CacheManager.automaticContentCache.TryGetValue(key: id, out page)) + Page? parent = null; + // Check if we need to create a section, even + var sections = (frontMatter.SourcePathDirectory ?? string.Empty).Split('/', StringSplitOptions.RemoveEmptyEntries); + if (sections.Count() > 1) { - Page? parent = null; + parent = CreateSystemPage(sections[0], sections[0]); + } - // Check if we need to create a section, even - var sections = (frontMatter.SourcePath ?? string.Empty).Split('/', StringSplitOptions.RemoveEmptyEntries); - if (sections.Count() > 1) - { - parent = CreateSectionPage(sections[0]); - } + var newPage = new Page(frontMatter, this); + PostProcessPage(newPage, parent); + return newPage; + })); - page = new(frontMatter, this); - CacheManager.automaticContentCache.Add(id, page); - PostProcessPage(page, parent); - } + // get the page from the lazy object + var page = lazyPage.Value; + + if (originalPage is null || string.IsNullOrEmpty(originalPage?.Permalink)) + { + return page; } - if (page.Kind != Kind.index && !string.IsNullOrEmpty(originalPage?.Permalink)) + if (page.Kind != Kind.index) { - page.PagesReferences ??= new(); - page.PagesReferences!.Add(originalPage.Permalink!); + page.PagesReferences.Add(originalPage.Permalink); } - // TODO: still too hardcoded - if (page.Type != "tags" || originalPage is null) + // TODO: still too hardcoded to add the tags reference + if (page.Type != "tags") { return page; } - - originalPage.TagsReference ??= new(); originalPage.TagsReference!.Add(page); return page; } - /// - /// Creates the page for the site index. - /// - /// The relative path of the page. - /// The created page for the index. - private Page CreateIndexPage(string relativePath) - { - Page page = new(new() - { - Title = Title, - SourcePath = Path.Combine(relativePath, indexFileConst), - Kind = string.IsNullOrEmpty(relativePath) ? Kind.index : Kind.list, - Section = (string.IsNullOrEmpty(relativePath) ? Kind.index : Kind.list).ToString(), - URL = "/", - Type = "index", - }, this); - - PostProcessPage(page); - return page; - } - /// /// Extra calculation and automatic data for each page. /// @@ -407,7 +370,6 @@ public class Site : IParams { if (old?.PagesReferences is not null) { - page.PagesReferences ??= new(); foreach (var pageOld in old.PagesReferences) { page.PagesReferences.Add(pageOld); @@ -426,7 +388,7 @@ public class Site : IParams // Register the page for all urls foreach (var url in page.Urls) { - PagesReferences[url] = page; + PagesReferences.TryAdd(url, page); } } } @@ -435,23 +397,13 @@ public class Site : IParams { foreach (var tagName in page.Tags) { - FrontMatter contentTemplate = new() - { - Title = tagName, - Section = "tags", - Type = "tags", - Kind = Kind.list, - URL = "tags/" + Urlizer.Urlize(tagName), - SourcePath = "tags/" + tagName - }; - CreateSystemPage(contentTemplate, page); + CreateSystemPage(Path.Combine("tags", tagName), tagName, "tags", page); } } if (!string.IsNullOrEmpty(page.Section) && PagesReferences.TryGetValue('/' + page.Section!, out var section)) { - section.PagesReferences ??= new(); section.PagesReferences.Add(page.Permalink!); } } diff --git a/source/Models/SiteCacheManager.cs b/source/Models/SiteCacheManager.cs index 137dd62..d984fe4 100644 --- a/source/Models/SiteCacheManager.cs +++ b/source/Models/SiteCacheManager.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Concurrent; using System.Collections.Generic; namespace SuCoS.Models; @@ -20,7 +22,7 @@ public class SiteCacheManager /// /// Cache for tag page. /// - public readonly Dictionary automaticContentCache = new(); + public readonly ConcurrentDictionary> automaticContentCache = new(); /// /// Resets the template cache to force a reload of all templates. diff --git a/source/Program.cs b/source/Program.cs index 24929ac..dfacd22 100644 --- a/source/Program.cs +++ b/source/Program.cs @@ -67,10 +67,10 @@ public class Program }; buildCommandHandler.SetHandler((source, output, future, verbose) => { - BuildOptions buildOptions = new() + BuildOptions buildOptions = new( + output: string.IsNullOrEmpty(output) ? Path.Combine(source, "public") : output) { Source = source, - Output = string.IsNullOrEmpty(output) ? Path.Combine(source, "public") : output, Future = future }; logger = new LoggerConfiguration() diff --git a/test/.TestSites/04-tags/content/blog/tags-01.md b/test/.TestSites/04-tags/content/blog/tags-01.md index 0f79798..1202151 100644 --- a/test/.TestSites/04-tags/content/blog/tags-01.md +++ b/test/.TestSites/04-tags/content/blog/tags-01.md @@ -1,8 +1,7 @@ --- -Title: Test Alias Tags: - tag1 - tag 2 --- -Test Alias +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-02.md b/test/.TestSites/04-tags/content/blog/tags-02.md index afcf002..1202151 100644 --- a/test/.TestSites/04-tags/content/blog/tags-02.md +++ b/test/.TestSites/04-tags/content/blog/tags-02.md @@ -1,8 +1,7 @@ --- -Title: Test Alias 2 Tags: - tag1 - tag 2 --- -Test Alias +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-03.md b/test/.TestSites/04-tags/content/blog/tags-03.md new file mode 100644 index 0000000..1202151 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-03.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-04.md b/test/.TestSites/04-tags/content/blog/tags-04.md new file mode 100644 index 0000000..1202151 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-04.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-05.md b/test/.TestSites/04-tags/content/blog/tags-05.md new file mode 100644 index 0000000..1202151 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-05.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-06.md b/test/.TestSites/04-tags/content/blog/tags-06.md new file mode 100644 index 0000000..1202151 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-06.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-07.md b/test/.TestSites/04-tags/content/blog/tags-07.md new file mode 100644 index 0000000..1202151 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-07.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-08.md b/test/.TestSites/04-tags/content/blog/tags-08.md new file mode 100644 index 0000000..1202151 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-08.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-09.md b/test/.TestSites/04-tags/content/blog/tags-09.md new file mode 100644 index 0000000..1202151 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-09.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag diff --git a/test/.TestSites/04-tags/content/blog/tags-10.md b/test/.TestSites/04-tags/content/blog/tags-10.md new file mode 100644 index 0000000..1202151 --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-10.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag diff --git a/test/BaseGeneratorCommandTests.cs b/test/BaseGeneratorCommandTests.cs index 02af054..1c377b0 100644 --- a/test/BaseGeneratorCommandTests.cs +++ b/test/BaseGeneratorCommandTests.cs @@ -8,7 +8,7 @@ namespace Test; public class BaseGeneratorCommandTests { - private static readonly IGenerateOptions testOptions = new BuildOptions + private static readonly IGenerateOptions testOptions = new GenerateOptions { Source = "test_source" }; diff --git a/test/Models/PageTests.cs b/test/Models/PageTests.cs index 710d24f..34a68d9 100644 --- a/test/Models/PageTests.cs +++ b/test/Models/PageTests.cs @@ -1,6 +1,5 @@ using System.Globalization; using Moq; -using SuCoS; using SuCoS.Models; using Xunit; using Serilog; @@ -14,6 +13,11 @@ public class PageTests private readonly Mock siteSettingsMock = new(); private readonly Mock loggerMock = new(); private readonly Mock systemClockMock = new(); + private readonly FrontMatter frontMatterMock = new() + { + Title = titleCONST, + SourcePath = sourcePathCONST + }; private readonly Site site; private const string titleCONST = "Test Title"; private const string sourcePathCONST = "/path/to/file.md"; @@ -51,7 +55,7 @@ word03 word04 word05 6 7 eight [InlineData("Test Title", "/path/to/file.md", "file", "/path/to")] public void Frontmatter_ShouldCreateWithCorrectProperties(string title, string sourcePath, string sourceFileNameWithoutExtension, string sourcePathDirectory) { - var page = new Page("Test Title", "/path/to/file.md", site); + var page = new Page(frontMatterMock, site); // Assert Assert.Equal(title, page.Title); @@ -65,7 +69,7 @@ word03 word04 word05 6 7 eight public void Frontmatter_ShouldHaveDefaultValuesForOptionalProperties() { // Arrange - var page = new Page("Test Title", "/path/to/file.md", site); + var page = new Page(frontMatterMock, site); // Assert Assert.Equal(string.Empty, page.Section); @@ -81,8 +85,8 @@ word03 word04 word05 6 7 eight Assert.Null(page.Permalink); Assert.Empty(page.Urls); Assert.Equal(string.Empty, page.RawContent); - Assert.Null(page.TagsReference); - Assert.Null(page.PagesReferences); + Assert.Empty(page.TagsReference); + Assert.Empty(page.PagesReferences); Assert.Empty(page.RegularPages); Assert.False(site.IsDateExpired(page)); Assert.True(site.IsDatePublishable(page)); @@ -115,8 +119,10 @@ word03 word04 word05 6 7 eight [InlineData(1, false)] public void IsDateExpired_ShouldReturnExpectedResult(int days, bool expected) { - var page = new Page(new(titleCONST, sourcePathCONST) + var page = new Page(new() { + Title = titleCONST, + SourcePath = sourcePathCONST, ExpiryDate = systemClockMock.Object.Now.AddDays(days) }, site); @@ -132,8 +138,10 @@ word03 word04 word05 6 7 eight [InlineData("2022-06-28", "2024-06-28", true)] public void IsDatePublishable_ShouldReturnCorrectValues(string? publishDate, string? date, bool expectedValue) { - var page = new Page(new(titleCONST, sourcePathCONST) + var page = new Page(new() { + Title = titleCONST, + SourcePath = sourcePathCONST, PublishDate = publishDate is null ? null : DateTime.Parse(publishDate, CultureInfo.InvariantCulture), Date = date is null ? null : DateTime.Parse(date, CultureInfo.InvariantCulture) }, site); @@ -147,8 +155,10 @@ word03 word04 word05 6 7 eight [InlineData(true, true)] public void IsValidDate_ShouldReturnExpectedResult(bool futureOption, bool expected) { - var page = new Page(new(titleCONST, sourcePathCONST) + var page = new Page(new() { + Title = titleCONST, + SourcePath = sourcePathCONST, Date = systemClockMock.Object.Now.AddDays(1) }, site); @@ -165,8 +175,9 @@ word03 word04 word05 6 7 eight [InlineData("/another/path/index.md", "/test-title")] public void CreatePermalink_ShouldReturnCorrectUrl_WhenUrlIsNull(string sourcePath, string expectedUrl) { - var page = new Page(new(titleCONST, sourcePathCONST) + var page = new Page(new() { + Title = titleCONST, SourcePath = sourcePath }, site); @@ -179,8 +190,10 @@ word03 word04 word05 6 7 eight [InlineData("{{ page.Title }}/{{ page.SourceFileNameWithoutExtension }}", "/test-title/file")] public void Permalink_CreateWithDefaultOrCustomURLTemplate(string urlTemplate, string expectedPermalink) { - var page = new Page(new(titleCONST, sourcePathCONST) + var page = new Page(new() { + Title = titleCONST, + SourcePath = sourcePathCONST, URL = urlTemplate }, site); var actualPermalink = page.CreatePermalink(); @@ -194,7 +207,7 @@ word03 word04 word05 6 7 eight [InlineData(Kind.list, false)] public void RegularPages_ShouldReturnCorrectPages_WhenKindIsSingle(Kind kind, bool isExpectedPage) { - var page = new Page(titleCONST, sourcePathCONST, site) { Kind = kind }; + var page = new Page(frontMatterMock, site) { Kind = kind }; // Act site.PostProcessPage(page); diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs index 79c7248..0f1d957 100644 --- a/test/Models/SiteTests.cs +++ b/test/Models/SiteTests.cs @@ -43,7 +43,7 @@ public class SiteTests { var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST01)); - site.Options = new BaseOptions() + site.Options = new GenerateOptions() { Source = siteFullPath }; @@ -61,7 +61,7 @@ public class SiteTests [InlineData(testSitePathCONST02)] public void Home_ShouldReturnAHomePage(string sitePath) { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) }; @@ -82,7 +82,7 @@ public class SiteTests [InlineData(testSitePathCONST03, 1)] public void Page_IsSection_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) }; @@ -91,6 +91,11 @@ public class SiteTests // Act site.ParseAndScanSourceFiles(null); + foreach (var page in site.PagesReferences.Values.Where(page => page.IsSection)) + { + Console.WriteLine(testSitePathCONST01 + " ---- " + page.Title); + } + // Assert Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsSection).Count()); } @@ -101,7 +106,7 @@ public class SiteTests [InlineData(testSitePathCONST03, 13)] public void PagesReference_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) }; @@ -120,7 +125,7 @@ public class SiteTests [InlineData(testSitePathCONST03, 11)] public void Page_IsPage_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) }; @@ -136,7 +141,7 @@ public class SiteTests [Fact] public void Page_Weight_ShouldReturnTheRightOrder() { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST03)) }; @@ -153,7 +158,7 @@ public class SiteTests [Fact] public void Page_Weight_ShouldReturnZeroWeight() { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST02)) }; @@ -170,7 +175,7 @@ public class SiteTests [Fact] public void TagSectionPage_Pages_ShouldReturnNumberTagPages() { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) }; @@ -184,15 +189,15 @@ public class SiteTests Assert.NotNull(tagSectionPage); Assert.Equal(2, tagSectionPage.Pages.Count()); Assert.Empty(tagSectionPage.RegularPages); - Assert.Equal("tags", tagSectionPage.SourcePath); - Assert.Equal(string.Empty, tagSectionPage.SourcePathDirectory); - Assert.Null(tagSectionPage.SourcePathLastDirectory); + Assert.Equal("tags/index.md", tagSectionPage.SourcePath); + Assert.Equal("tags", tagSectionPage.SourcePathDirectory); + Assert.Equal("tags", tagSectionPage.SourcePathLastDirectory); } [Fact] public void TagPage_Pages_ShouldReturnNumberReferences() { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) }; @@ -204,8 +209,8 @@ public class SiteTests // Assert site.PagesReferences.TryGetValue("/tags/tag1", out var page); Assert.NotNull(page); - Assert.Equal(2, page.Pages.Count()); - Assert.Equal(2, page.RegularPages.Count()); + Assert.Equal(10, page.Pages.Count()); + Assert.Equal(10, page.RegularPages.Count()); } [Theory] @@ -216,7 +221,7 @@ public class SiteTests [InlineData("/blog/test-content-1", "

Test Content 1

\n")] public void Page_Content_ShouldReturnNullThemeContent(string url, string expectedContent) { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) }; @@ -250,7 +255,7 @@ public class SiteTests "SINGLE-

Test Content 1

\n")] public void Page_Content_ShouldReturnNullThemeBaseofContent(string url, string expectedContentPreRendered, string expectedContent) { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST05)) }; @@ -275,7 +280,7 @@ public class SiteTests [InlineData("/blog/test-content-1")] public void Page_Content_ShouldReturnThrowNullThemeBaseofContent(string url) { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST07)) }; @@ -314,7 +319,7 @@ public class SiteTests "BASEOF-SINGLE-

Test Content 1

\n")] public void Page_Content_ShouldReturnThemeContent(string url, string expectedContentPreRendered, string expectedContent, string expectedOutputfile) { - BaseOptions options = new() + GenerateOptions options = new() { Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST06)) }; diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs index af340bd..1030fde 100644 --- a/test/Parser/YAMLParserTests.cs +++ b/test/Parser/YAMLParserTests.cs @@ -159,7 +159,11 @@ Title [Fact] public void ParseParams_ShouldFillParamsWithNonMatchingFields() { - var page = new Page("Test Title", "/test.md", siteDefault); + var page = new Page(new() + { + Title = "Test Title", + SourcePath = "/test.md", + }, siteDefault); // Act parser.ParseParams(page, typeof(Page), pageFrontmaterCONST); -- GitLab From b45034bb8a50a656c2e69add445586c46f81ca60 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 12 Jul 2023 00:38:30 -0300 Subject: [PATCH 8/9] refactor: YAMLParser injection --- source/Helpers/SiteHelper.cs | 15 ++- .../Models/CommandLineOptions/BuildOptions.cs | 2 +- .../CommandLineOptions/GenerateOptions.cs | 4 +- source/Models/FrontMatter.cs | 24 ++-- source/Models/Page.cs | 18 +-- source/Models/Site.cs | 115 ++++++++---------- test/Models/FrontMatterTests.cs | 10 +- test/Models/PageTests.cs | 20 +-- test/Models/SiteTests.cs | 11 +- test/Parser/YAMLParserTests.cs | 10 +- 10 files changed, 110 insertions(+), 119 deletions(-) diff --git a/source/Helpers/SiteHelper.cs b/source/Helpers/SiteHelper.cs index 4f9c57d..33d31b9 100644 --- a/source/Helpers/SiteHelper.cs +++ b/source/Helpers/SiteHelper.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Fluid; +using Markdig; using Microsoft.Extensions.FileProviders; using Serilog; using SuCoS.Models; @@ -14,6 +15,14 @@ namespace SuCoS.Helpers; ///
public static class SiteHelper { + /// + /// Markdig 20+ built-in extensions + /// + /// https://github.com/xoofx/markdig + public static readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + /// /// Creates the pages dictionary. /// @@ -35,7 +44,7 @@ public static class SiteHelper throw new FormatException("Error reading app config"); } - var site = new Site(options, siteSettings, logger, null); + var site = new Site(options, siteSettings, frontMatterParser, logger, null); // Liquid template options, needed to theme the content // but also parse URLs @@ -109,8 +118,8 @@ public static class SiteHelper } var fileContent = File.ReadAllText(filePath); - var site = frontMatterParser.ParseSiteSettings(fileContent) ?? throw new FormatException("Error reading app config"); - return site; + var siteSettings = frontMatterParser.ParseSiteSettings(fileContent) ?? throw new FormatException("Error reading app config"); + return siteSettings; } catch { diff --git a/source/Models/CommandLineOptions/BuildOptions.cs b/source/Models/CommandLineOptions/BuildOptions.cs index 02751e7..208de26 100644 --- a/source/Models/CommandLineOptions/BuildOptions.cs +++ b/source/Models/CommandLineOptions/BuildOptions.cs @@ -14,7 +14,7 @@ public class BuildOptions : GenerateOptions /// Constructor ///
/// - public BuildOptions(string output) : base() + public BuildOptions(string output) { Output = output; } diff --git a/source/Models/CommandLineOptions/GenerateOptions.cs b/source/Models/CommandLineOptions/GenerateOptions.cs index 1ed1fc2..709691b 100644 --- a/source/Models/CommandLineOptions/GenerateOptions.cs +++ b/source/Models/CommandLineOptions/GenerateOptions.cs @@ -6,8 +6,8 @@ namespace SuCoS.Models.CommandLineOptions; public class GenerateOptions : IGenerateOptions { /// - public string Source { get; set; } = "."; + public string Source { get; init; } = "."; /// - public bool Future { get; set; } + public bool Future { get; init; } } diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs index 5f42379..854c046 100644 --- a/source/Models/FrontMatter.cs +++ b/source/Models/FrontMatter.cs @@ -9,12 +9,12 @@ namespace SuCoS.Models; /// A scafold structure to help creating system-generated content, like /// tag, section or index pages ///
-public class FrontMatter : IFrontMatter, IParams +public class FrontMatter : IFrontMatter { #region IFrontMatter /// - public string? Title { get; set; } = string.Empty; + public string? Title { get; init; } = string.Empty; /// public string? Type { get; set; } = "page"; @@ -23,28 +23,28 @@ public class FrontMatter : IFrontMatter, IParams public string? URL { get; init; } /// - public List? Aliases { get; set; } + public List? Aliases { get; init; } /// public string? Section { get; set; } = string.Empty; /// - public DateTime? Date { get; set; } + public DateTime? Date { get; init; } /// - public DateTime? LastMod { get; set; } + public DateTime? LastMod { get; init; } /// - public DateTime? PublishDate { get; set; } + public DateTime? PublishDate { get; init; } /// - public DateTime? ExpiryDate { get; set; } + public DateTime? ExpiryDate { get; init; } /// - public int Weight { get; set; } = 0; + public int Weight { get; init; } = 0; /// - public List? Tags { get; set; } + public List? Tags { get; init; } /// [YamlIgnore] @@ -70,14 +70,10 @@ public class FrontMatter : IFrontMatter, IParams [YamlIgnore] public DateTime? GetPublishDate => PublishDate ?? Date; - #endregion IFrontMatter - - #region IParams - /// public Dictionary Params { get; set; } = new(); - #endregion IParams + #endregion IFrontMatter /// /// Constructor diff --git a/source/Models/Page.cs b/source/Models/Page.cs index 73f6aad..9e5541e 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -14,7 +14,7 @@ namespace SuCoS.Models; /// public class Page : IFrontMatter { - readonly FrontMatter frontMatter; + private readonly IFrontMatter frontMatter; #region IFrontMatter @@ -58,7 +58,7 @@ public class Page : IFrontMatter public Kind Kind { get => frontMatter.Kind; - set => frontMatter.Kind = value; + set => (frontMatter as FrontMatter)!.Kind = value; } /// @@ -90,7 +90,7 @@ public class Page : IFrontMatter /// /// Point to the site configuration. /// - public Site Site { get; set; } + public Site Site { get; init; } /// /// Secondary URL patterns to be used to create the url. @@ -117,7 +117,7 @@ public class Page : IFrontMatter /// /// Plain markdown content, without HTML. /// - public string Plain => Markdown.ToPlainText(RawContent, Site.MarkdownPipeline); + public string Plain => Markdown.ToPlainText(RawContent, SiteHelper.MarkdownPipeline); /// /// A list of tags, if any. @@ -144,7 +144,7 @@ public class Page : IFrontMatter /// public int WordCount => Plain.Split(nonWords, StringSplitOptions.RemoveEmptyEntries).Length; - private static readonly char[] nonWords = new[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r' }; + private static readonly char[] nonWords = { ' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r' }; /// /// The markdown content converted to HTML @@ -232,7 +232,7 @@ public class Page : IFrontMatter /// /// The markdown content. /// - private Lazy contentPreRenderedCached => new(() => Markdown.ToHtml(RawContent, Site.MarkdownPipeline)); + private Lazy contentPreRenderedCached => new(() => Markdown.ToHtml(RawContent, SiteHelper.MarkdownPipeline)); /// /// The cached content. @@ -269,7 +269,7 @@ endif /// /// Constructor /// - public Page(FrontMatter frontMatter, Site site) + public Page(IFrontMatter frontMatter, Site site) { this.frontMatter = frontMatter; Site = site; @@ -305,7 +305,7 @@ endif } catch (Exception ex) { - Site.Logger?.Error(ex, "Error converting URL: {URLforce}", URLforce); + Site.Logger.Error(ex, "Error converting URL: {URLforce}", URLforce); } if (!Path.IsPathRooted(permaLink) && !permaLink.StartsWith('/')) @@ -334,7 +334,7 @@ endif } catch (Exception ex) { - Site.Logger?.Error(ex, errorMessage, error); + Site.Logger.Error(ex, errorMessage, error); return string.Empty; } } diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 93808a8..980eea2 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Fluid; -using Markdig; using Serilog; using SuCoS.Models.CommandLineOptions; using SuCoS.Parser; @@ -37,13 +36,14 @@ public class Site : IParams #region SiteSettings - /// + /// + /// Site Title. + /// public string Title => settings.Title; - /// - public string BaseUrl => settings.BaseUrl; - - /// + /// + /// The appearance of a URL is either ugly or pretty. + /// public bool UglyURLs => settings.UglyURLs; #endregion SiteSettings @@ -71,7 +71,7 @@ public class Site : IParams /// /// List of all pages, including generated. /// - public List Pages + public IEnumerable Pages { get { @@ -82,16 +82,6 @@ public class Site : IParams } } - /// - /// Expose a page getter to templates. - /// - /// - /// - public Page? GetPage(string permalink) - { - return PagesReferences.TryGetValue(permalink, out var page) ? page : null; - } - /// /// List of all pages, including generated, by their permalink. /// @@ -121,7 +111,7 @@ public class Site : IParams /// /// Manage all caching lists for the site /// - public SiteCacheManager CacheManager = new(); + public readonly SiteCacheManager CacheManager = new(); /// /// The Fluid parser instance. @@ -136,17 +126,7 @@ public class Site : IParams /// /// The logger instance. /// - public ILogger Logger; - - /// - /// The time that the older cache should be ignored. - /// - public DateTime IgnoreCacheBefore { get; private set; } - - /// - /// Datetime wrapper - /// - public readonly ISystemClock Clock; + public ILogger Logger { get; } /// /// Number of files parsed, used in the report. @@ -165,7 +145,7 @@ public class Site : IParams /// /// The front matter parser instance. The default is YAML. /// - private readonly IFrontMatterParser frontMatterParser = new YAMLParser(); + private readonly IFrontMatterParser frontMatterParser; private List? pagesCache; @@ -173,14 +153,20 @@ public class Site : IParams private readonly SiteSettings settings; + /// + /// Datetime wrapper + /// + private readonly ISystemClock Clock; + /// /// Constructor /// - public Site(IGenerateOptions options, SiteSettings settings, ILogger logger, ISystemClock? clock) + public Site(IGenerateOptions options, SiteSettings settings, IFrontMatterParser frontMatterParser, ILogger logger, ISystemClock? clock) { Options = options; this.settings = settings; Logger = logger; + this.frontMatterParser = frontMatterParser; // Liquid template options, needed to theme the content // but also parse URLs @@ -190,14 +176,6 @@ public class Site : IParams Clock = clock ?? new SystemClock(); } - /// - /// Markdig 20+ built-in extensions - /// - /// https://github.com/xoofx/markdig - public readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder() - .UseAdvancedExtensions() - .Build(); - /// /// Resets the template cache to force a reload of all templates. /// @@ -221,16 +199,33 @@ public class Site : IParams var markdownFiles = Directory.GetFiles(directory, "*.md"); - var indexPath = markdownFiles.FirstOrDefault(file => Path.GetFileName(file).ToUpperInvariant() == indexFileUpperConst); - if (indexPath != null) + ParseIndexPage(directory, level, ref parent, ref markdownFiles); + + _ = Parallel.ForEach(markdownFiles, filePath => + { + ParseSourceFile(parent, filePath); + }); + + var subdirectories = Directory.GetDirectories(directory); + _ = Parallel.ForEach(subdirectories, subdirectory => + { + ParseAndScanSourceFiles(subdirectory, level + 1, parent); + }); + } + + private void ParseIndexPage(string? directory, int level, ref Page? parent, ref string[] markdownFiles) + { + // Check if the index.md file exists in the current directory + var indexPage = markdownFiles.FirstOrDefault(file => Path.GetFileName(file).ToUpperInvariant() == indexFileUpperConst); + if (indexPage is not null) { - markdownFiles = markdownFiles.Where(file => file != indexPath).ToArray(); - var page = ParseSourceFile(parent, indexPath); + markdownFiles = markdownFiles.Where(file => file != indexPage).ToArray(); + var page = ParseSourceFile(parent, indexPage); if (level == 0) { - PagesReferences.TryRemove(page!.Permalink!, out var _); + PagesReferences.TryRemove(page!.Permalink!, out _); Home = page; - page!.Permalink = "/"; + page.Permalink = "/"; page.Kind = Kind.index; PagesReferences.GetOrAdd(page.Permalink, page); } @@ -239,27 +234,19 @@ public class Site : IParams parent = page; } } + + // If it's the home page else if (level == 0) { - // TODO: unify the with section creation process Home = CreateSystemPage(string.Empty, Title); } + + // Or a section page, which must be used as the parent for the next sub folder else if (level == 1) { - var section = new DirectoryInfo(directory).Name; + var section = new DirectoryInfo(directory!).Name; parent = CreateSystemPage(section, section); } - - _ = Parallel.ForEach(markdownFiles, filePath => - { - ParseSourceFile(parent, filePath); - }); - - var subdirectories = Directory.GetDirectories(directory); - foreach (var subdirectory in subdirectories) - { - ParseAndScanSourceFiles(subdirectory, level + 1, parent); - } } private Page? ParseSourceFile(Page? parent, string filePath) @@ -278,7 +265,7 @@ public class Site : IParams } catch (Exception ex) { - Logger?.Error(ex, "Error parsing file {file}", filePath); + Logger.Error(ex, "Error parsing file {file}", filePath); } // Use interlocked to safely increment the counter in a multi-threaded environment @@ -306,18 +293,18 @@ public class Site : IParams SourcePath = Path.Combine(relativePath, indexFileConst), Title = title, Type = isIndex ? "index" : sectionName, - URL = relativePath, + URL = relativePath }; var id = frontMatter.URL; - + // Get or create the page var lazyPage = CacheManager.automaticContentCache.GetOrAdd(id, new Lazy(() => { Page? parent = null; // Check if we need to create a section, even var sections = (frontMatter.SourcePathDirectory ?? string.Empty).Split('/', StringSplitOptions.RemoveEmptyEntries); - if (sections.Count() > 1) + if (sections.Length > 1) { parent = CreateSystemPage(sections[0], sections[0]); } @@ -330,7 +317,7 @@ public class Site : IParams // get the page from the lazy object var page = lazyPage.Value; - if (originalPage is null || string.IsNullOrEmpty(originalPage?.Permalink)) + if (originalPage is null || string.IsNullOrEmpty(originalPage.Permalink)) { return page; } @@ -345,7 +332,7 @@ public class Site : IParams { return page; } - originalPage.TagsReference!.Add(page); + originalPage.TagsReference.Add(page); return page; } diff --git a/test/Models/FrontMatterTests.cs b/test/Models/FrontMatterTests.cs index 3f1c259..8c672f6 100644 --- a/test/Models/FrontMatterTests.cs +++ b/test/Models/FrontMatterTests.cs @@ -13,7 +13,7 @@ public class FrontMatterTests public void Constructor_Sets_Properties_Correctly(string title, string section, string type, string url, Kind kind) { // Act - var basicContent = new FrontMatter() + var basicContent = new FrontMatter { Title = title, Section = section, @@ -70,12 +70,12 @@ public class FrontMatterTests public void GetPublishDate_Returns_PublishDate_If_Not_Null_Otherwise_Date(string dateString, string publishDateString, string expectedDateString) { // Arrange - var date = dateString == null ? (DateTime?)null : DateTime.Parse(dateString, CultureInfo.InvariantCulture); - var publishDate = publishDateString == null ? (DateTime?)null : DateTime.Parse(publishDateString, CultureInfo.InvariantCulture); - var expectedDate = expectedDateString == null ? (DateTime?)null : DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); + var date = string.IsNullOrEmpty(dateString) ? (DateTime?)null : DateTime.Parse(dateString, CultureInfo.InvariantCulture); + var publishDate = string.IsNullOrEmpty(publishDateString) ? (DateTime?)null : DateTime.Parse(publishDateString, CultureInfo.InvariantCulture); + var expectedDate = string.IsNullOrEmpty(expectedDateString) ? (DateTime?)null : DateTime.Parse(expectedDateString, CultureInfo.InvariantCulture); // Act - var frontMatter = new FrontMatter() { Date = date, PublishDate = publishDate }; + var frontMatter = new FrontMatter { Date = date, PublishDate = publishDate }; // Assert Assert.Equal(expectedDate, frontMatter.GetPublishDate); diff --git a/test/Models/PageTests.cs b/test/Models/PageTests.cs index 34a68d9..fbf7431 100644 --- a/test/Models/PageTests.cs +++ b/test/Models/PageTests.cs @@ -4,11 +4,13 @@ using SuCoS.Models; using Xunit; using Serilog; using SuCoS.Models.CommandLineOptions; +using SuCoS.Parser; namespace Test.Models; public class PageTests { + private readonly IFrontMatterParser frontMatterParser = new YAMLParser(); private readonly Mock generateOptionsMock = new(); private readonly Mock siteSettingsMock = new(); private readonly Mock loggerMock = new(); @@ -48,7 +50,7 @@ word03 word04 word05 6 7 eight { var testDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); systemClockMock.Setup(c => c.Now).Returns(testDate); - site = new Site(generateOptionsMock.Object, siteSettingsMock.Object, loggerMock.Object, systemClockMock.Object); + site = new Site(generateOptionsMock.Object, siteSettingsMock.Object, frontMatterParser, loggerMock.Object, systemClockMock.Object); } [Theory] @@ -97,7 +99,7 @@ word03 word04 word05 6 7 eight [InlineData("/test-title-2")] public void Aliases_ShouldParseAsUrls(string url) { - var page = new Page(new() + var page = new Page(new FrontMatter { Title = titleCONST, SourcePath = sourcePathCONST, @@ -119,7 +121,7 @@ word03 word04 word05 6 7 eight [InlineData(1, false)] public void IsDateExpired_ShouldReturnExpectedResult(int days, bool expected) { - var page = new Page(new() + var page = new Page(new FrontMatter { Title = titleCONST, SourcePath = sourcePathCONST, @@ -138,7 +140,7 @@ word03 word04 word05 6 7 eight [InlineData("2022-06-28", "2024-06-28", true)] public void IsDatePublishable_ShouldReturnCorrectValues(string? publishDate, string? date, bool expectedValue) { - var page = new Page(new() + var page = new Page(new FrontMatter { Title = titleCONST, SourcePath = sourcePathCONST, @@ -155,7 +157,7 @@ word03 word04 word05 6 7 eight [InlineData(true, true)] public void IsValidDate_ShouldReturnExpectedResult(bool futureOption, bool expected) { - var page = new Page(new() + var page = new Page(new FrontMatter { Title = titleCONST, SourcePath = sourcePathCONST, @@ -175,7 +177,7 @@ word03 word04 word05 6 7 eight [InlineData("/another/path/index.md", "/test-title")] public void CreatePermalink_ShouldReturnCorrectUrl_WhenUrlIsNull(string sourcePath, string expectedUrl) { - var page = new Page(new() + var page = new Page(new FrontMatter { Title = titleCONST, SourcePath = sourcePath @@ -190,7 +192,7 @@ word03 word04 word05 6 7 eight [InlineData("{{ page.Title }}/{{ page.SourceFileNameWithoutExtension }}", "/test-title/file")] public void Permalink_CreateWithDefaultOrCustomURLTemplate(string urlTemplate, string expectedPermalink) { - var page = new Page(new() + var page = new Page(new FrontMatter { Title = titleCONST, SourcePath = sourcePathCONST, @@ -221,7 +223,7 @@ word03 word04 word05 6 7 eight [InlineData(markdown2CONST, 8)] public void WordCount_ShouldReturnCorrectCounts(string rawContent, int wordCountExpected) { - var page = new Page(new() + var page = new Page(new FrontMatter { RawContent = rawContent }, site); @@ -235,7 +237,7 @@ word03 word04 word05 6 7 eight [InlineData(markdown2CONST, markdownPlain2CONST)] public void Plain_ShouldReturnCorrectPlainString(string rawContent, string plain) { - var page = new Page(new() + var page = new Page(new FrontMatter { RawContent = rawContent }, site); diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs index 0f1d957..cefd4df 100644 --- a/test/Models/SiteTests.cs +++ b/test/Models/SiteTests.cs @@ -4,6 +4,7 @@ using System.Globalization; using SuCoS.Models; using Serilog; using SuCoS.Models.CommandLineOptions; +using SuCoS.Parser; namespace Test.Models; @@ -16,6 +17,7 @@ public class SiteTests private readonly Mock generateOptionsMock = new(); private readonly Mock siteSettingsMock = new(); private readonly Mock loggerMock = new(); + private readonly IFrontMatterParser frontMatterParser = new YAMLParser(); private readonly Mock systemClockMock = new(); private const string testSitePathCONST01 = ".TestSites/01"; private const string testSitePathCONST02 = ".TestSites/02-have-index"; @@ -33,7 +35,7 @@ public class SiteTests { var testDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); systemClockMock.Setup(c => c.Now).Returns(testDate); - site = new Site(generateOptionsMock.Object, siteSettingsMock.Object, loggerMock.Object, systemClockMock.Object); + site = new Site(generateOptionsMock.Object, siteSettingsMock.Object, frontMatterParser, loggerMock.Object, systemClockMock.Object); } [Theory] @@ -43,7 +45,7 @@ public class SiteTests { var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST01)); - site.Options = new GenerateOptions() + site.Options = new GenerateOptions { Source = siteFullPath }; @@ -91,11 +93,6 @@ public class SiteTests // Act site.ParseAndScanSourceFiles(null); - foreach (var page in site.PagesReferences.Values.Where(page => page.IsSection)) - { - Console.WriteLine(testSitePathCONST01 + " ---- " + page.Title); - } - // Assert Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsSection).Count()); } diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs index 1030fde..7bc74f6 100644 --- a/test/Parser/YAMLParserTests.cs +++ b/test/Parser/YAMLParserTests.cs @@ -56,7 +56,7 @@ NestedData: public YAMLParserTests() { parser = new YAMLParser(); - siteDefault = new Site(generateOptionsMock.Object, siteSettingsMock.Object, loggerMock.Object, systemClockMock.Object); + siteDefault = new Site(generateOptionsMock.Object, siteSettingsMock.Object, parser, loggerMock.Object, systemClockMock.Object); pageContent = @$"--- {pageFrontmaterCONST} --- @@ -159,10 +159,10 @@ Title [Fact] public void ParseParams_ShouldFillParamsWithNonMatchingFields() { - var page = new Page(new() + var page = new Page(new FrontMatter { Title = "Test Title", - SourcePath = "/test.md", + SourcePath = "/test.md" }, siteDefault); // Act @@ -198,7 +198,7 @@ Title siteDefault.PostProcessPage(page); // Asset - Assert.Equal(2, page.TagsReference?.Count); + Assert.Equal(2, page.TagsReference.Count); } [Fact] @@ -292,6 +292,6 @@ Title Assert.True(siteDefault.Params.ContainsKey("customParam")); Assert.Equal("Custom Value", siteDefault.Params["customParam"]); Assert.Equal(new[] { "Test", "Real Data" }, ((Dictionary)siteDefault.Params["NestedData"])["Level2"]); - Assert.Equal("Test", ((siteDefault?.Params["NestedData"] as Dictionary)?["Level2"] as List)?[0]); + Assert.Equal("Test", ((siteDefault.Params["NestedData"] as Dictionary)?["Level2"] as List)?[0]); } } -- GitLab From 0bf6177b59f2be38aaa22631f536dadbd31a0828 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 12 Jul 2023 00:54:53 -0300 Subject: [PATCH 9/9] fix: some Qodana recommendations --- .gitignore | 4 +++- source/BaseGeneratorCommand.cs | 40 ++++++++++++++++------------------ source/Models/Page.cs | 4 ++-- source/Models/Site.cs | 26 ++++++++++++---------- source/ServeCommand.cs | 38 +++++++++++++++++++++++++------- 5 files changed, 69 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 0de7bb9..ec824ea 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,6 @@ artifacts/ output/ project.fragment.lock.json project.lock.json -**/coverage-results/ \ No newline at end of file +**/coverage-results/ +.idea/ +qodana.yaml diff --git a/source/BaseGeneratorCommand.cs b/source/BaseGeneratorCommand.cs index 5428be0..dcafd2e 100644 --- a/source/BaseGeneratorCommand.cs +++ b/source/BaseGeneratorCommand.cs @@ -97,31 +97,29 @@ public abstract class BaseGeneratorCommand private static bool CheckValueInDictionary(string[] array, IReadOnlyDictionary dictionary, string value) { - var key = array[0]; - - // If the key doesn't exist or the value is not a dictionary, return false - if (!dictionary.TryGetValue(key, out var dictionaryValue)) + var currentDictionary = dictionary; + for (var i = 0; i < array.Length; i++) { - return false; - } + var key = array[i]; - // 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); - } + if (!currentDictionary.TryGetValue(key, out var dictionaryValue)) + { + return false; + } - // Check if the value is another dictionary - if (dictionaryValue is not Dictionary nestedDictionary) - { - return false; - } + if (i == array.Length - 1) + { + return dictionaryValue.Equals(value); + } - // Create a new array without the current key - var newArray = new string[array.Length - 1]; - Array.Copy(array, 1, newArray, 0, newArray.Length); + if (dictionaryValue is not Dictionary nestedDictionary) + { + return false; + } - // Recursively call the method with the nested dictionary and the new array - return CheckValueInDictionary(newArray, nestedDictionary, value); + currentDictionary = nestedDictionary; + } + return false; } + } diff --git a/source/Models/Page.cs b/source/Models/Page.cs index 9e5541e..fc793a4 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -90,7 +90,7 @@ public class Page : IFrontMatter /// /// Point to the site configuration. /// - public Site Site { get; init; } + public Site Site { get; } /// /// Secondary URL patterns to be used to create the url. @@ -269,7 +269,7 @@ endif /// /// Constructor /// - public Page(IFrontMatter frontMatter, Site site) + public Page(in IFrontMatter frontMatter, in Site site) { this.frontMatter = frontMatter; Site = site; diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 980eea2..03fa08f 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -156,12 +156,16 @@ public class Site : IParams /// /// Datetime wrapper /// - private readonly ISystemClock Clock; + private readonly ISystemClock clock; /// /// Constructor /// - public Site(IGenerateOptions options, SiteSettings settings, IFrontMatterParser frontMatterParser, ILogger logger, ISystemClock? clock) + public Site( + in IGenerateOptions options, + in SiteSettings settings, + in IFrontMatterParser frontMatterParser, + in ILogger logger, ISystemClock? clock) { Options = options; this.settings = settings; @@ -173,7 +177,7 @@ public class Site : IParams TemplateOptions.MemberAccessStrategy.Register(); TemplateOptions.MemberAccessStrategy.Register(); - Clock = clock ?? new SystemClock(); + this.clock = clock ?? new SystemClock(); } /// @@ -213,7 +217,7 @@ public class Site : IParams }); } - private void ParseIndexPage(string? directory, int level, ref Page? parent, ref string[] markdownFiles) + private void ParseIndexPage(in string? directory, int level, ref Page? parent, ref string[] markdownFiles) { // Check if the index.md file exists in the current directory var indexPage = markdownFiles.FirstOrDefault(file => Path.GetFileName(file).ToUpperInvariant() == indexFileUpperConst); @@ -249,7 +253,7 @@ public class Site : IParams } } - private Page? ParseSourceFile(Page? parent, string filePath) + private Page? ParseSourceFile(in Page? parent, in string filePath) { Page? page = null; try @@ -342,7 +346,7 @@ public class Site : IParams /// The given page to be processed /// The parent page, if any /// - public void PostProcessPage(Page page, Page? parent = null, bool overwrite = false) + public void PostProcessPage(in Page page, Page? parent = null, bool overwrite = false) { if (page is null) { @@ -401,7 +405,7 @@ public class Site : IParams /// Page or front matter /// options /// - public bool IsValidDate(IFrontMatter frontMatter, IGenerateOptions? options) + public bool IsValidDate(in IFrontMatter frontMatter, IGenerateOptions? options) { if (frontMatter is null) { @@ -413,24 +417,24 @@ public class Site : IParams /// /// Check if the page is expired /// - public bool IsDateExpired(IFrontMatter frontMatter) + public bool IsDateExpired(in IFrontMatter frontMatter) { if (frontMatter is null) { throw new ArgumentNullException(nameof(frontMatter)); } - return frontMatter.ExpiryDate is not null && frontMatter.ExpiryDate <= Clock.Now; + return frontMatter.ExpiryDate is not null && frontMatter.ExpiryDate <= clock.Now; } /// /// Check if the page is publishable /// - public bool IsDatePublishable(IFrontMatter frontMatter) + public bool IsDatePublishable(in IFrontMatter frontMatter) { if (frontMatter is null) { throw new ArgumentNullException(nameof(frontMatter)); } - return frontMatter.GetPublishDate is null || frontMatter.GetPublishDate <= Clock.Now; + return frontMatter.GetPublishDate is null || frontMatter.GetPublishDate <= clock.Now; } } \ No newline at end of file diff --git a/source/ServeCommand.cs b/source/ServeCommand.cs index 403f860..fac2bd6 100644 --- a/source/ServeCommand.cs +++ b/source/ServeCommand.cs @@ -331,18 +331,40 @@ public class ServeCommand : BaseGeneratorCommand, IDisposable // File changes are firing multiple events in a short time. // Debounce the event handler to prevent multiple events from firing in a short time debounceTimer?.Dispose(); - debounceTimer = new Timer(async _ => + debounceTimer = new Timer(DebounceCallback, e, TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan); + } + + private void DebounceCallback(object? state) + { + if (state is not FileSystemEventArgs e) { - if (restartInProgress) - { - return; - } + return; + } + HandleFileChangeAsync(e).GetAwaiter().GetResult(); + } + + private async Task HandleFileChangeAsync(FileSystemEventArgs e) + { + if (restartInProgress) + { + return; + } - logger.Information("File change detected: {FullPath}", e.FullPath); + logger.Information("File change detected: {FullPath}", e.FullPath); - restartInProgress = true; + restartInProgress = true; + try + { await RestartServer(); + } + catch (Exception ex) + { + logger.Error(ex, "Failed to restart server."); + throw; + } + finally + { restartInProgress = false; - }, null, TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan); + } } } -- GitLab