diff --git a/.gitignore b/.gitignore index 0de7bb9b2d0bc3d6b4c16b3316312fc91266b19d..ec824ea1c152d4a5f31277da4f30c2cd83ce944e 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 76c17b0f9560eab587ed8f02ec1408a01094a407..dcafd2ef5fe0cda53f144b243fb47ddbcff3129a 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; @@ -26,9 +27,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 +58,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); } /// @@ -96,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/BuildCommand.cs b/source/BuildCommand.cs index beffae0838b422f66bf14b13fbc45f1ad5306a09..64db95611b7cc6e1061b0943afd4df4ca8895e95 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; @@ -11,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). @@ -19,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); @@ -30,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); @@ -47,13 +47,13 @@ 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.CompleteContent; 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/BuildOptions.cs b/source/BuildOptions.cs deleted file mode 100644 index fc272146c7a6c445fc7de657df22e4ed66fbdec7..0000000000000000000000000000000000000000 --- 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 963b662eca34de27caf83bd1864b4c2539705725..25c490e9d3189ab8536a19e97253e13f7c73a54b 100644 --- a/source/Helpers/FileUtils.cs +++ b/source/Helpers/FileUtils.cs @@ -12,33 +12,33 @@ 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. - /// Site data. + /// 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, SiteCacheManager cacheManager, 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) + if (cacheManager is null) { - throw new ArgumentNullException(nameof(site)); + throw new ArgumentNullException(nameof(cacheManager)); } - var index = (frontmatter.Section, frontmatter.Kind, frontmatter.Type); + 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)) 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 5dcc818b3161b6903aeb750c653483fea6443f8b..33d31b971e1027163fdc4af683a1cd7e06061085 100644 --- a/source/Helpers/SiteHelper.cs +++ b/source/Helpers/SiteHelper.cs @@ -1,9 +1,11 @@ using System; using System.IO; using Fluid; +using Markdig; using Microsoft.Extensions.FileProviders; using Serilog; using SuCoS.Models; +using SuCoS.Models.CommandLineOptions; using SuCoS.Parser; namespace SuCoS.Helpers; @@ -13,27 +15,41 @@ 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. /// /// - 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) { throw new ArgumentNullException(nameof(stopwatch)); } - Site site; + SiteSettings siteSettings; try { - site = ParseSettings(configFile, options, frontmatterParser, whereParamsFilter, logger); + siteSettings = ParseSettings(configFile, options, frontMatterParser); } catch { throw new FormatException("Error reading app config"); } + var site = new Site(options, siteSettings, frontMatterParser, 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"); @@ -78,20 +94,18 @@ 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 SiteSettings ParseSettings(string configFile, IGenerateOptions options, IFrontMatterParser frontMatterParser) { 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,22 +118,8 @@ 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"); - } - return site; + var siteSettings = frontMatterParser.ParseSiteSettings(fileContent) ?? throw new FormatException("Error reading app config"); + return siteSettings; } catch { diff --git a/source/Helpers/Urlizer.cs b/source/Helpers/Urlizer.cs index 1abc0ac7be750c79391773e2ca2f9dbfb2311cd9..0b83bfdcfe8e1e8826c6b568f9268cb0b1ac1a69 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/Models/BasicContent.cs b/source/Models/BasicContent.cs deleted file mode 100644 index f0f6f81f19f22c7575cd45b01b23bfbc37fe4dbe..0000000000000000000000000000000000000000 --- a/source/Models/BasicContent.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace SuCoS.Models; - -/// -/// A scafold structure to help creating system-generated content, like -/// tag, section or index pages -/// -public class BasicContent : IBaseContent -{ - /// - public string Title { get; } - - /// - public string Section { get; } - - /// - public Kind Kind { get; } - - /// - public string Type { get; } - - /// - public string URL { get; } - - /// - /// Constructor - /// - /// - /// - /// - /// - /// - public BasicContent(string title, string section, string type, string url, Kind kind = Kind.list) - { - Title = title; - Section = section; - Kind = kind; - Type = type; - URL = url; - } -} diff --git a/source/Models/CommandLineOptions/BuildOptions.cs b/source/Models/CommandLineOptions/BuildOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..208de262f6eac961e597487e3ef8f55d34ff1389 --- /dev/null +++ b/source/Models/CommandLineOptions/BuildOptions.cs @@ -0,0 +1,21 @@ +namespace SuCoS.Models.CommandLineOptions; + +/// +/// Command line options for the build command. +/// +public class BuildOptions : GenerateOptions +{ + /// + /// The path of the output files. + /// + public string Output { get; } + + /// + /// Constructor + /// + /// + public BuildOptions(string output) + { + Output = output; + } +} diff --git a/source/Models/CommandLineOptions/GenerateOptions.cs b/source/Models/CommandLineOptions/GenerateOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..709691b4f82b3c693badea9a3027d1b76402c4b2 --- /dev/null +++ b/source/Models/CommandLineOptions/GenerateOptions.cs @@ -0,0 +1,13 @@ +namespace SuCoS.Models.CommandLineOptions; + +/// +/// Basic Command line options for the serve and build command. +/// +public class GenerateOptions : IGenerateOptions +{ + /// + public string Source { get; init; } = "."; + + /// + public bool Future { get; init; } +} diff --git a/source/IGenerateOptions.cs b/source/Models/CommandLineOptions/IGenerateOptions.cs similarity index 70% rename from source/IGenerateOptions.cs rename to source/Models/CommandLineOptions/IGenerateOptions.cs index 585b7491a86aa6e47879a65a21ff866a21edcb7a..472bb541a09ad7e51b7681b168d6b40a33d74fd7 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. @@ -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 new file mode 100644 index 0000000000000000000000000000000000000000..9e2b5c969726d7babb800b916303453a30410b92 --- /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 : GenerateOptions +{ +} diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs new file mode 100644 index 0000000000000000000000000000000000000000..854c0460a6269423e128cbc2cf28fb42a44f54e1 --- /dev/null +++ b/source/Models/FrontMatter.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using YamlDotNet.Serialization; + +namespace SuCoS.Models; + +/// +/// A scafold structure to help creating system-generated content, like +/// tag, section or index pages +/// +public class FrontMatter : IFrontMatter +{ + #region IFrontMatter + + /// + public string? Title { get; init; } = string.Empty; + + /// + public string? Type { get; set; } = "page"; + + /// + public string? URL { get; init; } + + /// + public List? Aliases { get; init; } + + /// + public string? Section { get; set; } = string.Empty; + + /// + public DateTime? Date { get; init; } + + /// + public DateTime? LastMod { get; init; } + + /// + public DateTime? PublishDate { get; init; } + + /// + public DateTime? ExpiryDate { get; init; } + + /// + public int Weight { get; init; } = 0; + + /// + public List? Tags { get; init; } + + /// + [YamlIgnore] + public string RawContent { get; set; } = string.Empty; + + /// + [YamlIgnore] + public Kind Kind { get; set; } = Kind.single; + + /// + [YamlIgnore] + public string? SourcePath { get; set; } + + /// + [YamlIgnore] + public string? SourceFileNameWithoutExtension => Path.GetFileNameWithoutExtension(SourcePath); + + /// + [YamlIgnore] + public string? SourcePathDirectory => Path.GetDirectoryName(SourcePath); + + /// + [YamlIgnore] + public DateTime? GetPublishDate => PublishDate ?? Date; + + /// + public Dictionary Params { get; set; } = new(); + + #endregion IFrontMatter + + /// + /// Constructor + /// + public FrontMatter() { } + + /// + /// Constructor + /// + /// + /// + public FrontMatter(string title, string sourcePath) + { + Title = title; + SourcePath = sourcePath; + } +} diff --git a/source/Models/Frontmatter.cs b/source/Models/Frontmatter.cs deleted file mode 100644 index 6500798ce9d0a0d3423a82a4b0e2334db1990510..0000000000000000000000000000000000000000 --- a/source/Models/Frontmatter.cs +++ /dev/null @@ -1,465 +0,0 @@ -using System; -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; -using YamlDotNet.Serialization; - -namespace SuCoS.Models; - -/// -/// The meta data about each content Markdown file. -/// -public class Frontmatter : IBaseContent, IParams -{ - #region IBaseContent - - /// - 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; } - - #endregion IBaseContent - - #region IParams - - /// - [YamlIgnore] - public Dictionary Params { get; set; } = new(); - - #endregion IParams - - /// - /// Gets or sets the date of the page. - /// - public DateTime? Date { get; set; } - - /// - /// Gets or sets the last modification date of the page. - /// - public DateTime? LastMod { get; set; } - - /// - /// Gets or sets the publish date of the page. - /// - public DateTime? PublishDate { get; set; } - - /// - /// Gets or sets the expiry date of the page. - /// - public DateTime? ExpiryDate { get; set; } - - /// - /// The path of the file, if it's a file. - /// - public string? SourcePath { get; set; } - - /// - /// Secondary URL patterns to be used to create the url. - /// - public List? Aliases { get; set; } - - /// - /// Page weight. Useful for sorting. - /// - public int Weight { get; set; } = 0; - - /// - /// A list of tags, if any. - /// - public List? Tags { get; set; } - - /// - /// The source filename, without the extension. ;) - /// - [YamlIgnore] - public string? SourceFileNameWithoutExtension { get; set; } - - /// - /// The source directory of the file. - /// - [YamlIgnore] - public string? SourcePathDirectory { get; set; } - - /// - /// The source directory of the file. - /// - [YamlIgnore] - public string? SourcePathLastDirectory => new DirectoryInfo(SourcePathDirectory ?? string.Empty).Name; - - /// - /// 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; } - - /// - /// 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. - /// - [YamlIgnore] - public ConcurrentBag? PagesReferences { get; set; } - - /// - /// Other content that mention this content. - /// Used to create the tags list and Related Posts section. - /// - [YamlIgnore] - public Frontmatter? 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 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 - /// - [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 => Type == "page"; - - /// - /// The number of words in the main content - /// - [YamlIgnore] - public int WordCount => - Plain.Split(new char[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?' }, - StringSplitOptions.RemoveEmptyEntries).Length; - - /// - /// The markdown content converted to HTML - /// - public string ContentPreRendered - { - get - { - contentPreRenderedCached ??= Markdown.ToHtml(RawContent, Site.MarkdownPipeline); - return contentPreRenderedCached; - } - } - - /// - /// The processed content. - /// - public string Content - { - get - { - if (contentCacheTime is not null && !(Site.IgnoreCacheBefore > contentCacheTime)) - { - return contentCache!; - } - contentCache = CreateContent(); - contentCacheTime = clock.UtcNow; - return contentCache!; - } - } - - /// - /// Other content that mention this content. - /// Used to create the tags list and Related Posts section. - /// - public IEnumerable Pages - { - get - { - if (PagesReferences is null) - { - return new List(); - } - - if (pagesCached is not null) - { - return pagesCached; - } - - pagesCached ??= new(); - foreach (var permalink in PagesReferences) - { - pagesCached.Add(Site.PagesReferences[permalink]); - } - return pagesCached; - } - } - - /// - /// List of pages from the content folder. - /// - public IEnumerable RegularPages - { - get - { - regularPagesCache ??= Pages - .Where(frontmatter => frontmatter.Kind == Kind.single) - .ToList(); - return regularPagesCache; - } - } - - /// - /// Get all URLs related to this content. - /// - public List Urls - { - get - { - var urls = new List(); - if (Permalink is not null) - { - urls.Add(Permalink); - } - - if (AliasesProcessed is not null) - { - urls.AddRange(from aliases in AliasesProcessed - select aliases); - } - - return urls; - } - } - - /// - /// The markdown content. - /// - private string? contentPreRenderedCached { get; set; } - - /// - /// 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 -echo '/' -endif -if page.Title != '' -echo page.Title -else -echo page.SourcePathLastDirectory -endif --%}"; - private const string urlForNonIndex = @"{%- liquid -if page.Parent -echo page.Parent.Permalink -echo '/' -endif -if page.Title != '' -echo page.Title -else -echo page.SourceFileNameWithoutExtension -endif --%}"; - - private List? regularPagesCache; - - private List? pagesCached { get; set; } - - private DateTime? GetPublishDate => PublishDate ?? Date; - - private ISystemClock clock => Site.Clock; - - /// - /// Required. - /// - public Frontmatter( - string title, - string sourcePath, - Site site, - string? sourceFileNameWithoutExtension = null, - string? sourcePathDirectory = null) - { - Title = title; - Site = site; - SourcePath = sourcePath; - SourceFileNameWithoutExtension = sourceFileNameWithoutExtension ?? Path.GetFileNameWithoutExtension(sourcePath); - SourcePathDirectory = sourcePathDirectory ?? Path.GetDirectoryName(sourcePath) ?? string.Empty; - } - - /// - /// Constructor - /// - public Frontmatter() - { - } - - /// - /// Check if the page have a publishing date from the past. - /// - /// options - /// - public bool IsValidDate(IGenerateOptions? options) - { - return !IsDateExpired && (IsDatePublishable || (options?.Future ?? false)); - } - - /// - /// Gets the Permalink path for the file. - /// - /// The URL to consider. If null, we get frontmatter.URL - /// The output path. - public string CreatePermalink(string? URLforce = null) - { - var isIndex = SourceFileNameWithoutExtension == "index"; - - var permaLink = string.Empty; - - URLforce ??= URL ?? (isIndex ? urlForIndex : urlForNonIndex); - - try - { - if (Site.FluidParser.TryParse(URLforce, out var template, out var error)) - { - var context = new TemplateContext(Site.TemplateOptions) - .SetValue("page", this); - permaLink = template.Render(context); - } - else - { - throw new FormatException(error); - } - } - catch (Exception ex) - { - Site.Logger?.Error(ex, "Error converting URL: {URLforce}", URLforce); - } - - if (!Path.IsPathRooted(permaLink) && !permaLink.StartsWith('/')) - { - permaLink = '/' + permaLink; - } - - return Urlizer.UrlizePath(permaLink); - } - - /// - /// Creates the output file by applying the theme templates to the frontmatter 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 - // 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() - { - var fileContents = FileUtils.GetTemplate(Site.SourceThemePath, this, Site); - // Theme content - if (string.IsNullOrEmpty(fileContents)) - { - return ContentPreRendered; - } - - if (Site.FluidParser.TryParse(fileContents, out var template, out var error)) - { - var context = new TemplateContext(Site.TemplateOptions) - .SetValue("page", this); - try - { - var rendered = template.Render(context); - return rendered; - } - catch (Exception ex) - { - Site.Logger?.Error(ex, "Error rendering theme template: {Error}", error); - return string.Empty; - } - } - - Site.Logger?.Error("Error parsing theme template: {Error}", error); - return string.Empty; - - } -} diff --git a/source/Models/IBaseContent.cs b/source/Models/IBaseContent.cs deleted file mode 100644 index 886cd6eef93267d7bd96b7bdff57fa380c8b7294..0000000000000000000000000000000000000000 --- a/source/Models/IBaseContent.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace SuCoS.Models; - -/// -/// Basic structure needed to generate user content and system content -/// -public interface IBaseContent -{ - /// - /// The content Title. - /// - public string? Title { get; } - - /// - /// The directory where the content is located. - /// - /// - /// - /// If the content is located at content/blog/2021-01-01-Hello-World.md, - /// then the value of this property will be blog. - /// - string? Section { 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. - /// - string? Type { get; } - - /// - /// The URL pattern to be used to create the url. - /// - string? URL { get; } -} diff --git a/source/Models/IFrontMatter.cs b/source/Models/IFrontMatter.cs new file mode 100644 index 0000000000000000000000000000000000000000..a06b57016e48a4ab3230a5935f480a0395d4ac57 --- /dev/null +++ b/source/Models/IFrontMatter.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using SuCoS.Models.CommandLineOptions; + +namespace SuCoS.Models; + +/// +/// Basic structure needed to generate user content and system content +/// +public interface IFrontMatter : IParams +{ + /// + /// The content Title. + /// + public string? Title { get; } + + /// + /// The first directory where the content is located, inside content. + /// + /// + /// + /// If the content is located at content/blog/2021-01-01-Hello-World.md, + /// then the value of this property will be blog. + /// + string? Section { get; } + + /// + /// 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; } + + /// + /// 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; } + + /// + /// Last modification date of the page. + /// Useful to notify users that the content was updated. + /// + DateTime? LastMod { get; } + + /// + /// 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; } + + /// + /// Expiry date of the page. + /// + DateTime? ExpiryDate { get; } + + /// + /// 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; } + + /// + /// Raw content from the Markdown file, bellow the front matter. + /// + string RawContent { get; } + + /// + /// 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. + /// + Kind Kind { get; } + + /// + /// The source filename, without the extension. ;) + /// + public string? SourcePath { get; } + + /// + /// The source filename, without the extension. ;) + /// + string? SourceFileNameWithoutExtension { get; } + + /// + /// The source directory of the file, without the file name. + /// + 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 new file mode 100644 index 0000000000000000000000000000000000000000..fc793a40a5cff7bf7b549e87f7bc0b655185d822 --- /dev/null +++ b/source/Models/Page.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Fluid; +using Markdig; +using SuCoS.Helpers; + +namespace SuCoS.Models; + +/// +/// Each page data created from source files or from the system. +/// +public class Page : IFrontMatter +{ + private readonly IFrontMatter frontMatter; + + #region IFrontMatter + + /// + public string? Title => frontMatter.Title; + + /// + public string? Type => frontMatter.Type; + + /// + public string? URL => frontMatter.URL; + + /// + public List? Aliases => frontMatter.Aliases; + + /// + public string? Section => frontMatter.Section; + + /// + public DateTime? Date => frontMatter.Date; + + /// + public DateTime? LastMod => frontMatter.LastMod; + + /// + public DateTime? PublishDate => frontMatter.PublishDate; + + /// + public DateTime? ExpiryDate => frontMatter.ExpiryDate; + + /// + public int Weight => frontMatter.Weight; + + /// + public List? Tags => frontMatter.Tags; + + /// + public string RawContent => frontMatter.RawContent; + + /// + public Kind Kind + { + get => frontMatter.Kind; + set => (frontMatter as FrontMatter)!.Kind = value; + } + + /// + public string? SourcePath => frontMatter.SourcePath; + + /// + public string? SourceFileNameWithoutExtension => frontMatter.SourceFileNameWithoutExtension; + + /// + public string? SourcePathDirectory => frontMatter.SourcePathDirectory; + + /// + public Dictionary Params + { + get => frontMatter.Params; + set => frontMatter.Params = value; + } + + #endregion IFrontMatter + + /// + /// The source directory of the file. + /// + public string? SourcePathLastDirectory => string.IsNullOrEmpty(SourcePathDirectory) + ? null + : Path.GetFileName(Path.GetFullPath(SourcePathDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); + + + /// + /// Point to the site configuration. + /// + public Site Site { get; } + + /// + /// Secondary URL patterns to be used to create the url. + /// + public List? AliasesProcessed { get; set; } + + /// + /// The URL for the content. + /// + public string? Permalink { get; set; } + + /// + /// Other content that mention this content. + /// Used to create the tags list and Related Posts section. + /// + public ConcurrentBag PagesReferences { get; } = new(); + + /// + /// Other content that mention this content. + /// Used to create the tags list and Related Posts section. + /// + public Page? Parent { get; set; } + + /// + /// Plain markdown content, without HTML. + /// + public string Plain => Markdown.ToPlainText(RawContent, SiteHelper.MarkdownPipeline); + + /// + /// A list of tags, if any. + /// + public ConcurrentBag TagsReference { get; } = new(); + + /// + /// Just a simple check if the current page is the home page + /// + public bool IsHome => Site.Home == this; + + /// + /// Just a simple check if the current page is a section page + /// + public bool IsSection => Type == "section"; + + /// + /// Just a simple check if the current page is a "page" + /// + public bool IsPage => Kind == Kind.single; + + /// + /// The number of words in the main content + /// + public int WordCount => Plain.Split(nonWords, StringSplitOptions.RemoveEmptyEntries).Length; + + private static readonly char[] nonWords = { ' ', ',', ';', '.', '!', '"', '(', ')', '?', '\n', '\r' }; + + /// + /// The markdown content converted to HTML + /// + public string ContentPreRendered => contentPreRenderedCached.Value; + + /// + /// The processed content. + /// + public string Content + { + get + { + 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. + /// + public IEnumerable Pages + { + get + { + if (pagesCached is not null) + { + return pagesCached; + } + + pagesCached ??= new(); + foreach (var permalink in PagesReferences) + { + pagesCached.Add(Site.PagesReferences[permalink]); + } + return pagesCached; + } + } + + /// + /// List of pages from the content folder. + /// + public IEnumerable RegularPages + { + get + { + regularPagesCache ??= Pages + .Where(page => page.Kind == Kind.single) + .ToList(); + return regularPagesCache; + } + } + + /// + /// Get all URLs related to this content. + /// + public List Urls + { + get + { + var urls = new List(); + if (Permalink is not null) + { + urls.Add(Permalink); + } + + if (AliasesProcessed is not null) + { + urls.AddRange(from aliases in AliasesProcessed + select aliases); + } + + return urls; + } + } + + /// + /// The markdown content. + /// + private Lazy contentPreRenderedCached => new(() => Markdown.ToHtml(RawContent, SiteHelper.MarkdownPipeline)); + + /// + /// The cached content. + /// + private string? contentCache { get; set; } + + private const string urlForIndex = @"{%- liquid +if page.Parent +echo page.Parent.Permalink +echo '/' +endif +if page.Title != '' +echo page.Title +else +echo page.SourcePathLastDirectory +endif +-%}"; + private const string urlForNonIndex = @"{%- liquid +if page.Parent +echo page.Parent.Permalink +echo '/' +endif +if page.Title != '' +echo page.Title +else +echo page.SourceFileNameWithoutExtension +endif +-%}"; + + private List? regularPagesCache; + + private List? pagesCached { get; set; } + + /// + /// Constructor + /// + public Page(in IFrontMatter frontMatter, in Site site) + { + this.frontMatter = frontMatter; + Site = site; + } + + /// + /// Gets the Permalink path for the file. + /// + /// The URL to consider. If null use the predefined URL + /// The output path. + /// + /// + public string CreatePermalink(string? URLforce = null) + { + var isIndex = SourceFileNameWithoutExtension == "index"; + + var permaLink = string.Empty; + + URLforce ??= URL ?? (isIndex ? urlForIndex : urlForNonIndex); + + try + { + if (Site.FluidParser.TryParse(URLforce, out var template, out var error)) + { + var context = new TemplateContext(Site.TemplateOptions) + .SetValue("page", this); + permaLink = template.Render(context); + } + else + { + throw new FormatException(error); + } + } + catch (Exception ex) + { + Site.Logger.Error(ex, "Error converting URL: {URLforce}", URLforce); + } + + if (!Path.IsPathRooted(permaLink) && !permaLink.StartsWith('/')) + { + permaLink = $"/{permaLink}"; + } + + return Urlizer.UrlizePath(permaLink); + } + private string ParseAndRenderTemplate(bool isBaseTemplate, string errorMessage) + { + var fileContents = FileUtils.GetTemplate(Site.SourceThemePath, this, Site.CacheManager, isBaseTemplate); + if (string.IsNullOrEmpty(fileContents)) + { + return isBaseTemplate ? Content : ContentPreRendered; + } + + if (Site.FluidParser.TryParse(fileContents, out var template, out var error)) + { + var context = new TemplateContext(Site.TemplateOptions) + .SetValue("page", this); + try + { + var rendered = template.Render(context); + return rendered; + } + catch (Exception ex) + { + Site.Logger.Error(ex, errorMessage, error); + return string.Empty; + } + } + + Site.Logger.Error(errorMessage, error); + return string.Empty; + } +} diff --git a/source/Models/Site.cs b/source/Models/Site.cs index e950d0287211b8d9e30214ff7d76cfd82cd4e619..03fa08f0df03b3298aa29698695557f00ac8d657 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -1,15 +1,14 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Fluid; -using Markdig; using Serilog; -using SuCoS.Helpers; +using SuCoS.Models.CommandLineOptions; using SuCoS.Parser; -using YamlDotNet.Serialization; namespace SuCoS.Models; @@ -21,118 +20,98 @@ 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; + + #region SiteSettings /// - /// The base URL that will be used to build internal links. + /// Site Title. /// - public string BaseUrl { get; set; } = "./"; + public string Title => settings.Title; /// /// The appearance of a URL is either ugly or pretty. /// - public bool UglyURLs { get; set; } = false; + public bool UglyURLs => settings.UglyURLs; - /// - /// The base path of the source site files. - /// - [YamlIgnore] - public string SourceDirectoryPath { get; set; } = "./"; + #endregion SiteSettings /// /// 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. /// public string SourceThemeStaticPath => Path.Combine(SourceThemePath, "static"); - /// - /// The path where the generated site files will be saved. - /// - public string OutputPath { get; set; } = "./"; - /// /// List of all pages, including generated. /// - public List Pages + public IEnumerable Pages { get { - pagesCache ??= PagesReferences.Values.ToList(); + pagesCache ??= PagesReferences.Values + .OrderBy(page => -page.Weight) + .ToList(); return pagesCache!; } } - /// - /// Expose a page getter to templates. - /// - /// - /// - public Frontmatter? GetPage(string permalink) - { - return PagesReferences.TryGetValue(permalink, out var page) ? page : null; - } - /// /// 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. /// - public List RegularPages + public List RegularPages { 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; } } /// - /// 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 + /// 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 readonly SiteCacheManager CacheManager = new(); /// /// The Fluid parser instance. @@ -147,27 +126,16 @@ public class Site : IParams /// /// The logger instance. /// - public ILogger? Logger; + public ILogger Logger { get; } /// - /// The time that the older cache should be ignored. - /// - public DateTime IgnoreCacheBefore { get; private set; } - - /// - /// Datetime wrapper + /// Number of files parsed, used in the report. /// - public readonly ISystemClock Clock; + public int filesParsedToReport; - /// - /// Cache for tag frontmatter. - /// - private readonly Dictionary automaticContentCache = new(); + private const string indexFileConst = "index.md"; - /// - /// The synchronization lock object. - /// - private readonly object syncLock = new(); + private const string indexFileUpperConst = "INDEX.MD"; /// /// The synchronization lock object during ProstProcess. @@ -175,45 +143,41 @@ 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; - private List? pagesCache; + private List? pagesCache; - private List? regularPagesCache; + private List? regularPagesCache; - /// - /// Number of files parsed, used in the report. - /// - public int filesParsedToReport; + private readonly SiteSettings settings; /// - /// Markdig 20+ built-in extensions + /// Datetime wrapper /// - /// https://github.com/xoofx/markdig - public readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder() - .UseAdvancedExtensions() - .Build(); + private readonly ISystemClock clock; /// /// Constructor /// - public Site() : this(new SystemClock()) + public Site( + in IGenerateOptions options, + in SiteSettings settings, + in IFrontMatterParser frontMatterParser, + in ILogger logger, ISystemClock? clock) { - } + Options = options; + this.settings = settings; + Logger = logger; + this.frontMatterParser = frontMatterParser; - /// - /// Constructor - /// - public Site(ISystemClock clock) - { // Liquid template options, needed to theme the content // but also parse URLs - TemplateOptions.MemberAccessStrategy.Register(); + TemplateOptions.MemberAccessStrategy.Register(); TemplateOptions.MemberAccessStrategy.Register(); - Clock = clock; + this.clock = clock ?? new SystemClock(); } /// @@ -221,11 +185,8 @@ public class Site : IParams /// public void ResetCache() { - baseTemplateCache.Clear(); - contentTemplateCache.Clear(); - automaticContentCache.Clear(); + CacheManager.ResetCache(); PagesReferences.Clear(); - IgnoreCacheBefore = DateTime.Now; } /// @@ -234,227 +195,246 @@ 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, Frontmatter? pageParent = null) + public void ParseAndScanSourceFiles(string? directory, int level = 0, Page? parent = null) { directory ??= SourceContentPath; var markdownFiles = Directory.GetFiles(directory, "*.md"); - var indexPath = markdownFiles.FirstOrDefault(file => Path.GetFileName(file).ToUpperInvariant() == "INDEX.MD"); - 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(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); + if (indexPage is not null) { - markdownFiles = markdownFiles.Where(file => file != indexPath).ToArray(); - var frontmatter = ParseSourceFile(pageParent, indexPath); + markdownFiles = markdownFiles.Where(file => file != indexPage).ToArray(); + var page = ParseSourceFile(parent, indexPage); if (level == 0) { - Home = frontmatter; - frontmatter!.Permalink = "/"; - frontmatter.Kind = Kind.index; - PagesReferences.Remove(frontmatter.Permalink); - PagesReferences.Add(frontmatter.Permalink, frontmatter); + PagesReferences.TryRemove(page!.Permalink!, out _); + Home = page; + page.Permalink = "/"; + page.Kind = Kind.index; + PagesReferences.GetOrAdd(page.Permalink, page); } else { - pageParent = frontmatter; + parent = page; } } + + // If it's the home page else if (level == 0) { - // TODO: unify the with section creation process - Home = CreateIndexPage(string.Empty); - } - else if (level == 1) - { - var section = new DirectoryInfo(directory).Name; - var contentTemplate = new BasicContent( - title: section, - section: "section", - type: "section", - url: section - ); - pageParent = CreateAutomaticFrontmatter(contentTemplate, null); + Home = CreateSystemPage(string.Empty, Title); } - _ = Parallel.ForEach(markdownFiles, filePath => - { - ParseSourceFile(pageParent, filePath); - }); - - var subdirectories = Directory.GetDirectories(directory); - foreach (var subdirectory in subdirectories) + // Or a section page, which must be used as the parent for the next sub folder + else if (level == 1) { - ParseAndScanSourceFiles(subdirectory, level + 1, pageParent); + var section = new DirectoryInfo(directory!).Name; + parent = CreateSystemPage(section, section); } } - private Frontmatter? ParseSourceFile(Frontmatter? pageParent, string filePath) + private Page? ParseSourceFile(in Page? parent, in string filePath) { - Frontmatter? frontmatter = null; + Page? page = null; try { - frontmatter = frontmatterParser.ParseFrontmatterAndMarkdownFromFile(this, filePath, SourceContentPath) - ?? throw new FormatException($"Error parsing frontmatter for {filePath}"); + var frontMatter = frontMatterParser.ParseFrontmatterAndMarkdownFromFile(filePath, SourceContentPath) + ?? throw new FormatException($"Error parsing front matter for {filePath}"); - if (frontmatter.IsValidDate(options)) + if (IsValidDate(frontMatter, Options)) { - PostProcessFrontMatter(frontmatter, pageParent, true); + page = new(frontMatter, this); + PostProcessPage(page, parent, true); } } 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 _ = 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. + /// Creates the page for the site index. /// - public Frontmatter CreateAutomaticFrontmatter(BasicContent baseContent, Frontmatter? originalFrontmatter) + /// 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 (baseContent is null) + sectionName ??= "section"; + var isIndex = string.IsNullOrEmpty(relativePath); + FrontMatter frontMatter = new() { - throw new ArgumentNullException(nameof(baseContent)); - } + 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 = baseContent.URL; - Frontmatter? frontmatter; - lock (syncLock) + var id = frontMatter.URL; + + // Get or create the page + var lazyPage = CacheManager.automaticContentCache.GetOrAdd(id, new Lazy(() => { - if (!automaticContentCache.TryGetValue(id, out frontmatter)) + Page? parent = null; + // Check if we need to create a section, even + var sections = (frontMatter.SourcePathDirectory ?? string.Empty).Split('/', StringSplitOptions.RemoveEmptyEntries); + if (sections.Length > 1) { - frontmatter = new( - site: this, - title: baseContent.Title, - sourcePath: string.Empty, - sourceFileNameWithoutExtension: string.Empty, - sourcePathDirectory: null - ) - { - Section = baseContent.Section, - Kind = baseContent.Kind, - Type = baseContent.Type, - URL = baseContent.URL, - PagesReferences = new() - }; - automaticContentCache.Add(id, frontmatter); - PostProcessFrontMatter(frontmatter); + parent = CreateSystemPage(sections[0], sections[0]); } - } - if (frontmatter.Kind != Kind.index && originalFrontmatter?.Permalink is not null) - { - frontmatter.PagesReferences!.Add(originalFrontmatter.Permalink!); - } + var newPage = new Page(frontMatter, this); + PostProcessPage(newPage, parent); + return newPage; + })); - // TODO: still too hardcoded - if (frontmatter.Type != "tags" || originalFrontmatter is null) + // get the page from the lazy object + var page = lazyPage.Value; + + if (originalPage is null || string.IsNullOrEmpty(originalPage.Permalink)) { - return frontmatter; + return page; } - lock (originalFrontmatter) + + if (page.Kind != Kind.index) { - originalFrontmatter.TagsReference ??= new(); - originalFrontmatter.TagsReference!.Add(frontmatter); + page.PagesReferences.Add(originalPage.Permalink); } - return frontmatter; - } - /// - /// Creates the frontmatter for the index page. - /// - /// The relative path of the page. - /// The created frontmatter for the index page. - private Frontmatter CreateIndexPage(string relativePath) - { - Frontmatter frontmatter = new( - title: Title, - site: this, - sourcePath: Path.Combine(relativePath, "index.md"), - sourceFileNameWithoutExtension: "index", - sourcePathDirectory: "/" - ) + // TODO: still too hardcoded to add the tags reference + if (page.Type != "tags") { - Kind = string.IsNullOrEmpty(relativePath) ? Kind.index : Kind.list, - Section = (string.IsNullOrEmpty(relativePath) ? Kind.index : Kind.list).ToString(), - URL = "/" - }; - - PostProcessFrontMatter(frontmatter); - return frontmatter; + return page; + } + originalPage.TagsReference.Add(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(in 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) + 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.TryAdd(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( - title: tagName, - section: "tags", - type: "tags", - url: "tags/" + Urlizer.Urlize(tagName) - ); - _ = CreateAutomaticFrontmatter(contentTemplate, frontmatter); + CreateSystemPage(Path.Combine("tags", tagName), tagName, "tags", 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.Add(page.Permalink!); + } + } + + /// + /// Check if the page have a publishing date from the past. + /// + /// Page or front matter + /// options + /// + public bool IsValidDate(in 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(in 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(in IFrontMatter frontMatter) + { + if (frontMatter is null) { - section.PagesReferences ??= new(); - section.PagesReferences.Add(frontmatter.Permalink!); + throw new ArgumentNullException(nameof(frontMatter)); } + return frontMatter.GetPublishDate is null || frontMatter.GetPublishDate <= clock.Now; } } \ No newline at end of file diff --git a/source/Models/SiteCacheManager.cs b/source/Models/SiteCacheManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..d984fe46d307d6dc9e0d061f6446d0a4ea7c23fe --- /dev/null +++ b/source/Models/SiteCacheManager.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Concurrent; +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 ConcurrentDictionary> 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 0000000000000000000000000000000000000000..4de5802caa09343263c21079d9274456b7d2fb37 --- /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 0277a6069c92d63910e8f482452351a53037df69..1318dfb2959ae77fa672b43871eb54aaafd2ec38 100644 --- a/source/Parser/IFrontmatterParser.cs +++ b/source/Parser/IFrontmatterParser.cs @@ -3,32 +3,30 @@ 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); + FrontMatter? ParseFrontmatterAndMarkdownFromFile(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); + FrontMatter? ParseFrontmatterAndMarkdown(in string filePath, in string fileContent); /// /// Parse the app config file. /// /// /// - 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 f166971463000810c0f19961fb1b208ca382b0e2..9804569b457ce90119723ba8c5ec1e347cde7ad6 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,12 +29,8 @@ public class YAMLParser : IFrontmatterParser .Build(); /// - public Frontmatter 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,70 +48,61 @@ public class YAMLParser : IFrontmatterParser throw new FileNotFoundException(filePath, ex); } - return ParseFrontmatterAndMarkdown(site, fileRelativePath, fileContent); + return ParseFrontmatterAndMarkdown(fileRelativePath, fileContent); } /// - public Frontmatter 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)); } 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 - var page = ParseYAML(ref site, fileRelativePath, yaml, rawContent); + // Now, you can parse the YAML front matter + var page = ParseYAML(fileRelativePath, yaml, rawContent); return page; } - private Frontmatter 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 frontmatter"); - 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(Frontmatter), yaml, yamlDictionary); + ParseParams(frontMatter, typeof(Page), yaml, yamlDictionary); - return page; + return frontMatter; } /// - 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 87a217aaea2205cfc0eb732a70d1af1034875cc6..dfacd228a14a3baaec689c7710022c273fa2e0e5 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; @@ -66,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/source/ServeCommand.cs b/source/ServeCommand.cs index f34d763d1d3c723da87dcee1993854f60ac170c7..fac2bd6d8dee1833557e5c2ad5c8679a1f21024e 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; @@ -176,7 +177,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 +232,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 +259,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.CompleteContent; content = InjectReloadScript(content); await context.Response.WriteAsync(content); } @@ -330,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); + } } } diff --git a/source/ServeOptions.cs b/source/ServeOptions.cs deleted file mode 100644 index 86169f77a999664e1aec92d1ede1b6bc9e4cfdda..0000000000000000000000000000000000000000 --- a/source/ServeOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace SuCoS; - -/// -/// Command line options for the serve command. -/// -public class ServeOptions : IGenerateOptions -{ - /// - public string Source { get; set; } = "."; - - /// - public string? Output { get; set; } - - /// - public bool Future { get; set; } -} diff --git a/test/.TestSites/01/content/categories.md b/test/.TestSites/01/content/categories.md new file mode 100644 index 0000000000000000000000000000000000000000..4065d6776966561b2a554f975ce3a4f8ddc12cc3 --- /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 c1f358660e0a0a1f73fe7b0b82b449f871d20418..5a6a90e5de9f9e1e24e3d30844dc0b1650093c53 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 0000000000000000000000000000000000000000..d4363e77c005f5f26ff9b868a88f76a44132ee40 --- /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 0000000000000000000000000000000000000000..02ae1bb158ad5629aa30a2f2f865c35f0bd060d7 --- /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 0000000000000000000000000000000000000000..70e8da6865b549260a5781cb6981c0e51f2d2dda --- /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 0000000000000000000000000000000000000000..f4507ff8076505302863b8b779b7063852b8b865 --- /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 7dfb40d58b564b8177d0abbb482489925a4b0f52..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..44e58b4dcb404c62bd28269e8f7002911827f750 --- /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 0000000000000000000000000000000000000000..4065d6776966561b2a554f975ce3a4f8ddc12cc3 --- /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 0000000000000000000000000000000000000000..5a6a90e5de9f9e1e24e3d30844dc0b1650093c53 --- /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 0000000000000000000000000000000000000000..d4363e77c005f5f26ff9b868a88f76a44132ee40 --- /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 0000000000000000000000000000000000000000..02ae1bb158ad5629aa30a2f2f865c35f0bd060d7 --- /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 0000000000000000000000000000000000000000..28806b1107b8fe86f837e4d7f37fe8cab4da73cb --- /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 0000000000000000000000000000000000000000..70e8da6865b549260a5781cb6981c0e51f2d2dda --- /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 0000000000000000000000000000000000000000..f4507ff8076505302863b8b779b7063852b8b865 --- /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 0000000000000000000000000000000000000000..2d3c4158bf543cd81271fdb19c46fd6442f43d12 --- /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 0000000000000000000000000000000000000000..44e58b4dcb404c62bd28269e8f7002911827f750 --- /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 0000000000000000000000000000000000000000..4065d6776966561b2a554f975ce3a4f8ddc12cc3 --- /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 0000000000000000000000000000000000000000..5a6a90e5de9f9e1e24e3d30844dc0b1650093c53 --- /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 0000000000000000000000000000000000000000..d4363e77c005f5f26ff9b868a88f76a44132ee40 --- /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 0000000000000000000000000000000000000000..02ae1bb158ad5629aa30a2f2f865c35f0bd060d7 --- /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 0000000000000000000000000000000000000000..70e8da6865b549260a5781cb6981c0e51f2d2dda --- /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 0000000000000000000000000000000000000000..f4507ff8076505302863b8b779b7063852b8b865 --- /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 0000000000000000000000000000000000000000..2d3c4158bf543cd81271fdb19c46fd6442f43d12 --- /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/.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 0000000000000000000000000000000000000000..974d2c97c3820ad784a3f2add5338ba46ba1e1a5 --- /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 0000000000000000000000000000000000000000..e36b408ce5d772396153d019ccfd601033e68950 --- /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 0000000000000000000000000000000000000000..2355b30a7a08557ee60e82a2529569fe856f94ef --- /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 0000000000000000000000000000000000000000..08cfa38c0f4abd16fdf23d6aec9d210855bb0192 --- /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 0000000000000000000000000000000000000000..4065d6776966561b2a554f975ce3a4f8ddc12cc3 --- /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 0000000000000000000000000000000000000000..5a6a90e5de9f9e1e24e3d30844dc0b1650093c53 --- /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 0000000000000000000000000000000000000000..d4363e77c005f5f26ff9b868a88f76a44132ee40 --- /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 0000000000000000000000000000000000000000..02ae1bb158ad5629aa30a2f2f865c35f0bd060d7 --- /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 0000000000000000000000000000000000000000..70e8da6865b549260a5781cb6981c0e51f2d2dda --- /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 0000000000000000000000000000000000000000..f4507ff8076505302863b8b779b7063852b8b865 --- /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 0000000000000000000000000000000000000000..44e58b4dcb404c62bd28269e8f7002911827f750 --- /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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-01.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# Test Tag 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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /dev/null +++ b/test/.TestSites/04-tags/content/blog/tags-02.md @@ -0,0 +1,7 @@ +--- +Tags: + - tag1 + - tag 2 +--- + +# 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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /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 0000000000000000000000000000000000000000..12021517467131ac332e10978a0441305a2a6e9e --- /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/.TestSites/04-tags/content/blog/test01.md b/test/.TestSites/04-tags/content/blog/test01.md new file mode 100644 index 0000000000000000000000000000000000000000..2d3c4158bf543cd81271fdb19c46fd6442f43d12 --- /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 0000000000000000000000000000000000000000..974d2c97c3820ad784a3f2add5338ba46ba1e1a5 --- /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 0000000000000000000000000000000000000000..e36b408ce5d772396153d019ccfd601033e68950 --- /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 0000000000000000000000000000000000000000..2355b30a7a08557ee60e82a2529569fe856f94ef --- /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 0000000000000000000000000000000000000000..08cfa38c0f4abd16fdf23d6aec9d210855bb0192 --- /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 0000000000000000000000000000000000000000..28806b1107b8fe86f837e4d7f37fe8cab4da73cb --- /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 0000000000000000000000000000000000000000..44e58b4dcb404c62bd28269e8f7002911827f750 --- /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 0000000000000000000000000000000000000000..4065d6776966561b2a554f975ce3a4f8ddc12cc3 --- /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 0000000000000000000000000000000000000000..5a6a90e5de9f9e1e24e3d30844dc0b1650093c53 --- /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 0000000000000000000000000000000000000000..d4363e77c005f5f26ff9b868a88f76a44132ee40 --- /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 0000000000000000000000000000000000000000..02ae1bb158ad5629aa30a2f2f865c35f0bd060d7 --- /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 0000000000000000000000000000000000000000..70e8da6865b549260a5781cb6981c0e51f2d2dda --- /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 0000000000000000000000000000000000000000..f4507ff8076505302863b8b779b7063852b8b865 --- /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 0000000000000000000000000000000000000000..0f79798ae02ae5f7f9b6ece7896dee8a6b52b973 --- /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 0000000000000000000000000000000000000000..afcf002e2af3163a5172ccc937d262f9d6770faa --- /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 0000000000000000000000000000000000000000..2d3c4158bf543cd81271fdb19c46fd6442f43d12 --- /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 0000000000000000000000000000000000000000..974d2c97c3820ad784a3f2add5338ba46ba1e1a5 --- /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 0000000000000000000000000000000000000000..e36b408ce5d772396153d019ccfd601033e68950 --- /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 0000000000000000000000000000000000000000..2355b30a7a08557ee60e82a2529569fe856f94ef --- /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 0000000000000000000000000000000000000000..08cfa38c0f4abd16fdf23d6aec9d210855bb0192 --- /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 0000000000000000000000000000000000000000..28806b1107b8fe86f837e4d7f37fe8cab4da73cb --- /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 0000000000000000000000000000000000000000..28806b1107b8fe86f837e4d7f37fe8cab4da73cb --- /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 0000000000000000000000000000000000000000..6b9b3b0fab933af83895db7650711e913178379b --- /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 0000000000000000000000000000000000000000..df270cf40e8553db1f7e0fb27756bd2aa22b3e37 --- /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 0000000000000000000000000000000000000000..bea53d90e0fde93f4135fd6cd2c081f8317bf03b --- /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 0000000000000000000000000000000000000000..44e58b4dcb404c62bd28269e8f7002911827f750 --- /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 0000000000000000000000000000000000000000..4065d6776966561b2a554f975ce3a4f8ddc12cc3 --- /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 0000000000000000000000000000000000000000..5a6a90e5de9f9e1e24e3d30844dc0b1650093c53 --- /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 0000000000000000000000000000000000000000..d4363e77c005f5f26ff9b868a88f76a44132ee40 --- /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 0000000000000000000000000000000000000000..02ae1bb158ad5629aa30a2f2f865c35f0bd060d7 --- /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 0000000000000000000000000000000000000000..70e8da6865b549260a5781cb6981c0e51f2d2dda --- /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 0000000000000000000000000000000000000000..f4507ff8076505302863b8b779b7063852b8b865 --- /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 0000000000000000000000000000000000000000..0f79798ae02ae5f7f9b6ece7896dee8a6b52b973 --- /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 0000000000000000000000000000000000000000..afcf002e2af3163a5172ccc937d262f9d6770faa --- /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 0000000000000000000000000000000000000000..2d3c4158bf543cd81271fdb19c46fd6442f43d12 --- /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 0000000000000000000000000000000000000000..974d2c97c3820ad784a3f2add5338ba46ba1e1a5 --- /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 0000000000000000000000000000000000000000..e36b408ce5d772396153d019ccfd601033e68950 --- /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 0000000000000000000000000000000000000000..2355b30a7a08557ee60e82a2529569fe856f94ef --- /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 0000000000000000000000000000000000000000..08cfa38c0f4abd16fdf23d6aec9d210855bb0192 --- /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 0000000000000000000000000000000000000000..28806b1107b8fe86f837e4d7f37fe8cab4da73cb --- /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 0000000000000000000000000000000000000000..28806b1107b8fe86f837e4d7f37fe8cab4da73cb --- /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 0000000000000000000000000000000000000000..e6f3bedade2ba218dc17826871efdcb82470e09e --- /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 0000000000000000000000000000000000000000..6b9b3b0fab933af83895db7650711e913178379b --- /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 0000000000000000000000000000000000000000..df270cf40e8553db1f7e0fb27756bd2aa22b3e37 --- /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 0000000000000000000000000000000000000000..bea53d90e0fde93f4135fd6cd2c081f8317bf03b --- /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 0000000000000000000000000000000000000000..44e58b4dcb404c62bd28269e8f7002911827f750 --- /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 0000000000000000000000000000000000000000..4065d6776966561b2a554f975ce3a4f8ddc12cc3 --- /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 0000000000000000000000000000000000000000..5a6a90e5de9f9e1e24e3d30844dc0b1650093c53 --- /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 0000000000000000000000000000000000000000..d4363e77c005f5f26ff9b868a88f76a44132ee40 --- /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 0000000000000000000000000000000000000000..02ae1bb158ad5629aa30a2f2f865c35f0bd060d7 --- /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 0000000000000000000000000000000000000000..70e8da6865b549260a5781cb6981c0e51f2d2dda --- /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 0000000000000000000000000000000000000000..f4507ff8076505302863b8b779b7063852b8b865 --- /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 0000000000000000000000000000000000000000..0f79798ae02ae5f7f9b6ece7896dee8a6b52b973 --- /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 0000000000000000000000000000000000000000..afcf002e2af3163a5172ccc937d262f9d6770faa --- /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 0000000000000000000000000000000000000000..2d3c4158bf543cd81271fdb19c46fd6442f43d12 --- /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 0000000000000000000000000000000000000000..974d2c97c3820ad784a3f2add5338ba46ba1e1a5 --- /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 0000000000000000000000000000000000000000..e36b408ce5d772396153d019ccfd601033e68950 --- /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 0000000000000000000000000000000000000000..2355b30a7a08557ee60e82a2529569fe856f94ef --- /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 0000000000000000000000000000000000000000..08cfa38c0f4abd16fdf23d6aec9d210855bb0192 --- /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 0000000000000000000000000000000000000000..28806b1107b8fe86f837e4d7f37fe8cab4da73cb --- /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 0000000000000000000000000000000000000000..28806b1107b8fe86f837e4d7f37fe8cab4da73cb --- /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 0000000000000000000000000000000000000000..4a27853ca58e2cb1ac0d8bc59b614ecbfe983b39 --- /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 0000000000000000000000000000000000000000..e77db28ffc1d43d6ea691548aaeb51258a4edadf --- /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 0000000000000000000000000000000000000000..bd6be74f170b42f212b2779a43eb292a2fb49136 --- /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/BaseGeneratorCommandTests.cs b/test/BaseGeneratorCommandTests.cs index 0b863699ef8a0ae09f852a0f6733eeb6d8988a73..1c377b0ed5e37abcf80f2998c192b521e6be5e6f 100644 --- a/test/BaseGeneratorCommandTests.cs +++ b/test/BaseGeneratorCommandTests.cs @@ -1,13 +1,14 @@ using System.Reflection; using Serilog; using SuCoS; +using SuCoS.Models.CommandLineOptions; using Xunit; 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/BasicContentTests.cs b/test/Models/BasicContentTests.cs deleted file mode 100644 index 1241e9e2a518b9acdb1ebdd120f13df39fda5845..0000000000000000000000000000000000000000 --- 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 BasicContent(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 BasicContent(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 0000000000000000000000000000000000000000..8c672f611a22ceba32b40238bf95bf83c0fff1ab --- /dev/null +++ b/test/Models/FrontMatterTests.cs @@ -0,0 +1,84 @@ +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 = title, + Section = section, + Type = type, + URL = url, + Kind = 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); + } + + [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 = 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 }; + + // Assert + Assert.Equal(expectedDate, frontMatter.GetPublishDate); + Assert.Equal(expectedDate, (frontMatter as IFrontMatter).GetPublishDate); + } +} diff --git a/test/Models/FrontmatterTests.cs b/test/Models/FrontmatterTests.cs deleted file mode 100644 index 5140a0295083e724c638e13fce1b588dd55ada81..0000000000000000000000000000000000000000 --- a/test/Models/FrontmatterTests.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System.Globalization; -using Moq; -using SuCoS; -using SuCoS.Models; -using Xunit; - -namespace Test.Models; - -public class FrontmatterTests -{ - 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() - { - var testDate = DateTime.Parse("2023-04-01", CultureInfo.InvariantCulture); - systemClockMock.Setup(c => c.Now).Returns(testDate); - clock = systemClockMock.Object; - site = new(clock); - } - - [Theory] - [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); - - // 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); - } - - [Fact] - public void Frontmatter_ShouldHaveDefaultValuesForOptionalProperties() - { - // Arrange - var frontmatter = new Frontmatter("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); - } - - [Fact] - public void Aliases_ShouldParseAsUrls() - { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) - { - Title = "Title", - Aliases = new() { "v123", "{{ page.Title }}" } - }; - - // Act - site.PostProcessFrontMatter(frontmatter); - - // Assert - foreach (var url in new[] { "/v123", "/title" }) - { - site.PagesReferences.TryGetValue(url, out var frontmatter1); - Assert.NotNull(frontmatter1); - Assert.Same(frontmatter, frontmatter1); - } - } - - [Theory] - [InlineData(-1, true)] - [InlineData(1, false)] - public void IsDateExpired_ShouldReturnExpectedResult(int days, bool expected) - { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) - { - ExpiryDate = clock.Now.AddDays(days) - }; - - // Assert - Assert.Equal(expected, frontmatter.IsDateExpired); - } - - [Theory] - [InlineData(null, null, true)] - [InlineData(null, "2024-06-28", false)] - [InlineData("2022-06-28", null, true)] - [InlineData("2024-06-28", "2022-06-28", false)] - [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) - { - 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); - } - - [Theory] - [InlineData(false, false)] - [InlineData(true, true)] - public void IsValidDate_ShouldReturnExpectedResult(bool futureOption, bool expected) - { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) - { - Date = clock.Now.AddDays(1) - }; - - // Act - var options = new Mock(); - options.Setup(o => o.Future).Returns(futureOption); - - // Assert - Assert.Equal(expected, frontmatter.IsValidDate(options.Object)); - } - - [Theory] - [InlineData("/test/path", "/test-title")] - [InlineData("/another/path", "/test-title")] - public void CreatePermalink_ShouldReturnCorrectUrl_WhenUrlIsNull(string sourcePathDirectory, string expectedUrl) - { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) - { - SourcePathDirectory = sourcePathDirectory - }; - - // Assert - Assert.Equal(expectedUrl, frontmatter.CreatePermalink()); - } - - [Theory] - [InlineData(null, "/test-title")] - [InlineData("{{ page.Title }}/{{ page.SourceFileNameWithoutExtension }}", "/test-title/file")] - public void Permalink_CreateWithDefaultOrCustomURLTemplate(string urlTemplate, string expectedPermalink) - { - var frontmatter = new Frontmatter(titleCONST, sourcePathCONST, site) - { - URL = urlTemplate - }; - var actualPermalink = frontmatter.CreatePermalink(); - - // Assert - Assert.Equal(expectedPermalink, actualPermalink); - } - - [Theory] - [InlineData(Kind.single, true)] - [InlineData(Kind.list, false)] - public void RegularPages_ShouldReturnCorrectPages_WhenKindIsSingle(Kind kind, bool isExpectedPage) - { - var page = new Frontmatter(titleCONST, sourcePathCONST, site) { Kind = kind }; - - // Act - site.PostProcessFrontMatter(page); - - // Assert - Assert.Equal(isExpectedPage, site.RegularPages.Contains(page)); - } -} diff --git a/test/Models/PageTests.cs b/test/Models/PageTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..fbf74317d8d3aa7fe009ed1336434f56e715f696 --- /dev/null +++ b/test/Models/PageTests.cs @@ -0,0 +1,248 @@ +using System.Globalization; +using Moq; +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(); + 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"; + 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); + site = new Site(generateOptionsMock.Object, siteSettingsMock.Object, frontMatterParser, loggerMock.Object, systemClockMock.Object); + } + + [Theory] + [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(frontMatterMock, site); + + // Assert + 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 page = new Page(frontMatterMock, site); + + // Assert + 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.Empty(page.TagsReference); + Assert.Empty(page.PagesReferences); + Assert.Empty(page.RegularPages); + Assert.False(site.IsDateExpired(page)); + Assert.True(site.IsDatePublishable(page)); + } + + [Theory] + [InlineData("/v123")] + [InlineData("/test-title-2")] + public void Aliases_ShouldParseAsUrls(string url) + { + var page = new Page(new FrontMatter + { + Title = titleCONST, + SourcePath = sourcePathCONST, + Aliases = new() { "v123", "{{ page.Title }}", "{{ page.Title }}-2" } + }, site); + + // Act + site.PostProcessPage(page); + + // Assert + Assert.Equal(3, site.PagesReferences.Count); + site.PagesReferences.TryGetValue(url, out var pageOther); + Assert.NotNull(pageOther); + Assert.Same(page, pageOther); + } + + [Theory] + [InlineData(-1, true)] + [InlineData(1, false)] + public void IsDateExpired_ShouldReturnExpectedResult(int days, bool expected) + { + var page = new Page(new FrontMatter + { + Title = titleCONST, + SourcePath = sourcePathCONST, + ExpiryDate = systemClockMock.Object.Now.AddDays(days) + }, site); + + // Assert + Assert.Equal(expected, site.IsDateExpired(page)); + } + + [Theory] + [InlineData(null, null, true)] + [InlineData(null, "2024-06-28", false)] + [InlineData("2022-06-28", null, true)] + [InlineData("2024-06-28", "2022-06-28", false)] + [InlineData("2022-06-28", "2024-06-28", true)] + public void IsDatePublishable_ShouldReturnCorrectValues(string? publishDate, string? date, bool expectedValue) + { + var page = new Page(new FrontMatter + { + 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); + + // Assert + Assert.Equal(expectedValue, site.IsDatePublishable(page)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, true)] + public void IsValidDate_ShouldReturnExpectedResult(bool futureOption, bool expected) + { + var page = new Page(new FrontMatter + { + Title = titleCONST, + SourcePath = sourcePathCONST, + Date = systemClockMock.Object.Now.AddDays(1) + }, site); + + // Act + var options = new Mock(); + options.Setup(o => o.Future).Returns(futureOption); + + // Assert + Assert.Equal(expected, site.IsValidDate(page, options.Object)); + } + + [Theory] + [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(new FrontMatter + { + Title = titleCONST, + SourcePath = sourcePath + }, site); + + // Assert + Assert.Equal(expectedUrl, page.CreatePermalink()); + } + + [Theory] + [InlineData(null, "/test-title")] + [InlineData("{{ page.Title }}/{{ page.SourceFileNameWithoutExtension }}", "/test-title/file")] + public void Permalink_CreateWithDefaultOrCustomURLTemplate(string urlTemplate, string expectedPermalink) + { + var page = new Page(new FrontMatter + { + Title = titleCONST, + SourcePath = sourcePathCONST, + URL = urlTemplate + }, site); + var actualPermalink = page.CreatePermalink(); + + // Assert + Assert.Equal(expectedPermalink, actualPermalink); + } + + [Theory] + [InlineData(Kind.single, true)] + [InlineData(Kind.list, false)] + public void RegularPages_ShouldReturnCorrectPages_WhenKindIsSingle(Kind kind, bool isExpectedPage) + { + var page = new Page(frontMatterMock, site) { Kind = kind }; + + // Act + site.PostProcessPage(page); + + // 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 FrontMatter + { + 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 FrontMatter + { + RawContent = rawContent + }, site); + + // Assert + Assert.Equal(plain, page.Plain); + } +} diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs index 5cb16d119c17154345a4b50cd95cac461a2204af..cefd4df012854de03cec89f5a5f0291bbaba17bc 100644 --- a/test/Models/SiteTests.cs +++ b/test/Models/SiteTests.cs @@ -1,8 +1,10 @@ using Xunit; using Moq; using System.Globalization; -using SuCoS.Helpers; using SuCoS.Models; +using Serilog; +using SuCoS.Models.CommandLineOptions; +using SuCoS.Parser; namespace Test.Models; @@ -12,8 +14,18 @@ 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 IFrontMatterParser frontMatterParser = new YAMLParser(); private readonly Mock systemClockMock = new(); - private const string testSite1PathCONST = ".TestSites/01"; + 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" @@ -23,39 +35,301 @@ 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, frontMatterParser, loggerMock.Object, systemClockMock.Object); } [Theory] [InlineData("test01.md")] - [InlineData("test02.md")] - public void Test_ScanAllMarkdownFiles(string fileName) + [InlineData("date-ok.md")] + 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)); + site.Options = new GenerateOptions + { + Source = siteFullPath + }; // 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.SourcePathDirectory!.Length == 0); Assert.Contains(site.Pages, page => page.SourceFileNameWithoutExtension == fileNameWithoutExtension); } [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) + [InlineData(testSitePathCONST01)] + [InlineData(testSitePathCONST02)] + public void Home_ShouldReturnAHomePage(string sitePath) { - 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)); + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + }; + site.Options = options; - site.ResetCache(); + // Act + site.ParseAndScanSourceFiles(site.SourceContentPath); + + // Assert + Assert.NotNull(site.Home); + Assert.True(site.Home.IsHome); + Assert.Single(site.PagesReferences.Values.Where(page => page.IsHome)); + } + + [Theory] + [InlineData(testSitePathCONST01, 0)] + [InlineData(testSitePathCONST02, 0)] + [InlineData(testSitePathCONST03, 1)] + public void Page_IsSection_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + }; + site.Options = options; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsSection).Count()); + } + + [Theory] + [InlineData(testSitePathCONST01, 5)] + [InlineData(testSitePathCONST02, 8)] + [InlineData(testSitePathCONST03, 13)] + public void PagesReference_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + }; + site.Options = options; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + Assert.Equal(expectedQuantity, site.PagesReferences.Count); + } + + [Theory] + [InlineData(testSitePathCONST01, 4)] + [InlineData(testSitePathCONST02, 7)] + [InlineData(testSitePathCONST03, 11)] + public void Page_IsPage_ShouldReturnExpectedQuantityOfPages(string sitePath, int expectedQuantity) + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, sitePath)) + }; + site.Options = options; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + Assert.Equal(expectedQuantity, site.PagesReferences.Values.Where(page => page.IsPage).Count()); + } + + [Fact] + public void Page_Weight_ShouldReturnTheRightOrder() + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST03)) + }; + site.Options = options; + + // 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() + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST02)) + }; + site.Options = options; + + // 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() + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + }; + site.Options = options; + + // 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/index.md", tagSectionPage.SourcePath); + Assert.Equal("tags", tagSectionPage.SourcePathDirectory); + Assert.Equal("tags", tagSectionPage.SourcePathLastDirectory); + } + + [Fact] + public void TagPage_Pages_ShouldReturnNumberReferences() + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + }; + site.Options = options; + + // Act + site.ParseAndScanSourceFiles(null); + + // Assert + site.PagesReferences.TryGetValue("/tags/tag1", out var page); + Assert.NotNull(page); + Assert.Equal(10, page.Pages.Count()); + Assert.Equal(10, 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) + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST04)) + }; + site.Options = options; - Assert.Empty(site.baseTemplateCache); - Assert.Empty(site.contentTemplateCache); - Assert.Empty(site.PagesReferences); + // 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) + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST05)) + }; + site.Options = options; + + // 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.CompleteContent); + } + + [Theory] + [InlineData("/")] + [InlineData("/blog")] + [InlineData("/tags")] + [InlineData("/tags/tag1")] + [InlineData("/blog/test-content-1")] + public void Page_Content_ShouldReturnThrowNullThemeBaseofContent(string url) + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST07)) + }; + site.Options = options; + + // 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.CompleteContent); + } + + [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) + { + GenerateOptions options = new() + { + Source = Path.GetFullPath(Path.Combine(testSitesPath, testSitePathCONST06)) + }; + site.Options = options; + + // 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.CompleteContent); } } diff --git a/test/Parser/YAMLParserTests.cs b/test/Parser/YAMLParserTests.cs index ecfefc4b5583a7a9abb1e84ea244ca67b51f3847..7bc74f6034abfe538ddf8657f5943b2417e0c4af 100644 --- a/test/Parser/YAMLParserTests.cs +++ b/test/Parser/YAMLParserTests.cs @@ -4,14 +4,19 @@ using SuCoS.Parser; using System.Globalization; using SuCoS.Helpers; using SuCoS.Models; -using Microsoft.VisualBasic; +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 @@ -51,7 +56,7 @@ NestedData: public YAMLParserTests() { parser = new YAMLParser(); - siteDefault = new Mock(); + siteDefault = new Site(generateOptionsMock.Object, siteSettingsMock.Object, parser, loggerMock.Object, systemClockMock.Object); pageContent = @$"--- {pageFrontmaterCONST} --- @@ -82,17 +87,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(filePathCONST, fileContent); // Asset - Assert.Equal(expectedTitle, frontmatter.Title); - } - - [Fact] - public void ParseFrontmatter_ShouldThrowException_WhenSiteIsNull() - { - // Asset - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(null!, filePathCONST, pageContent)); + Assert.Equal(expectedTitle, frontMatter.Title); } [Theory] @@ -109,10 +107,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(filePathCONST, fileContent); // Asset - Assert.Equal(expectedDate, frontmatter.Date); + Assert.Equal(expectedDate, frontMatter.Date); } [Fact] @@ -124,15 +122,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(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] @@ -144,7 +142,7 @@ Title "; // Asset - Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(siteDefault.Object, filePathCONST, fileContent)); + Assert.Throws(() => parser.ParseFrontmatterAndMarkdown(filePathCONST, fileContent)); } [Fact] @@ -161,10 +159,14 @@ Title [Fact] public void ParseParams_ShouldFillParamsWithNonMatchingFields() { - var page = new Frontmatter("Test Title", "/test.md", siteDefault.Object); + var page = new Page(new FrontMatter + { + Title = "Test Title", + SourcePath = "/test.md" + }, siteDefault); // Act - parser.ParseParams(page, typeof(Frontmatter), pageFrontmaterCONST); + parser.ParseParams(page, typeof(Page), pageFrontmaterCONST); // Asset Assert.True(page.Params.ContainsKey("customParam")); @@ -175,35 +177,39 @@ 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); // Act - siteDefault.Object.PostProcessFrontMatter(frontmatter); + siteDefault.PostProcessPage(page); // 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("", pageContent); + Page page = new(frontMatter, siteDefault); + + // Act + siteDefault.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"]); - 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] @@ -215,31 +221,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,9 +260,9 @@ 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); + Assert.Equal(pageMarkdownCONST, frontMatter.RawContent); } [Fact] @@ -268,24 +274,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]); } }