diff --git a/Tests/Models/FrontMatterTests.cs b/Tests/Models/FrontMatterTests.cs index 2b01efc7333590b27c6909fc9d0418373472b5d2..657a15c9f7a3e9966c96219c610586336b8c15ce 100644 --- a/Tests/Models/FrontMatterTests.cs +++ b/Tests/Models/FrontMatterTests.cs @@ -1,5 +1,5 @@ -using SuCoS.Models; using System.Globalization; +using SuCoS.Models; using Xunit; namespace Tests.Models; @@ -7,10 +7,10 @@ namespace Tests.Models; public class FrontMatterTests : TestSetup { [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) + [InlineData("Title1", "Section1", "Type1", "URL1")] + [InlineData("Title2", "Section2", "Type2", "URL2")] + [InlineData("Title3", "Section3", "Type3", "URL3")] + public void Constructor_Sets_Properties_Correctly(string title, string section, string type, string url) { // Act var basicContent = new FrontMatter @@ -18,8 +18,7 @@ public class FrontMatterTests : TestSetup Title = title, Section = section, Type = type, - URL = url, - Kind = kind + URL = url }; // Assert @@ -27,7 +26,6 @@ public class FrontMatterTests : TestSetup Assert.Equal(section, basicContent.Section); Assert.Equal(type, basicContent.Type); Assert.Equal(url, basicContent.URL); - Assert.Equal(kind, basicContent.Kind); } [Theory] diff --git a/Tests/Models/SiteTests.cs b/Tests/Models/SiteTests.cs index ee91f87b5d9ab1d7f5f855be5eeb452dff86ca4c..60cad0d94d4d78f7b5313ef3cfba2938221ca0de 100644 --- a/Tests/Models/SiteTests.cs +++ b/Tests/Models/SiteTests.cs @@ -74,7 +74,7 @@ public class SiteTests : TestSetup // Assert Assert.Equal(expectedQuantity, Site.OutputReferences.Values.Count(output => output is IPage { - IsSection: true + Kind: Kind.Section })); } diff --git a/source/Helpers/FileUtils.cs b/source/Helpers/FileUtils.cs index 7d7e7df921b5f8caadce3141872b04a94c0d5ee1..703d9b788db92fb50b9d13cb50a1957a8f5fce8c 100644 --- a/source/Helpers/FileUtils.cs +++ b/source/Helpers/FileUtils.cs @@ -74,15 +74,29 @@ public static class FileUtils // Generate the lookup order for template files based on the theme path, page section, type, and kind string[] sections = page.Section is not null ? [page.Section, string.Empty] : [string.Empty]; var types = new[] { page.Type, "_default" }; - string[] kinds = isBaseTemplate - ? [page.Kind.ToString().ToLower(System.Globalization.CultureInfo.CurrentCulture) + "-baseof", "baseof"] - : [page.Kind.ToString().ToLower(System.Globalization.CultureInfo.CurrentCulture)]; + + // Get all the kinds including the "sub-values" + var kinds = GetAllKinds(page.Kind, isBaseTemplate); + + if (isBaseTemplate) + { + kinds = kinds.Append("baseof"); + } // for each section, each type and each kind - return (from section in sections - from type in types - from kind in kinds - let path = Path.Combine(themePath, section, type!, kind) + ".liquid" - select path).ToList(); + return sections + .SelectMany(section => types.Select(type => new { section, type })) + .SelectMany(x => kinds.Select(kind => new { x.section, x.type, kind })) + .Select(x => Path.Combine(themePath, x.section, x.type!, x.kind) + ".liquid") + .ToList(); } + + private static IEnumerable GetAllKinds(Kind kind, bool isBaseTemplate) => + Enum.GetValues(typeof(Kind)) + .Cast() + .Where(k => kind.HasFlag(k)) + .OrderByDescending(k => k) + .Select(k => isBaseTemplate + ? k.ToString().ToLower(System.Globalization.CultureInfo.CurrentCulture) + "-baseof" + : k.ToString().ToLower(System.Globalization.CultureInfo.CurrentCulture)); } diff --git a/source/Models/FrontMatter.cs b/source/Models/FrontMatter.cs index 942b958c2c7b4354db0bdb77f1456cba1a5a9693..6f8587d31675d6a2b8fc6a33571fa6d828916d02 100644 --- a/source/Models/FrontMatter.cs +++ b/source/Models/FrontMatter.cs @@ -56,10 +56,6 @@ public class FrontMatter : IFrontMatter [YamlIgnore] public string RawContent { get; set; } = string.Empty; - /// - [YamlIgnore] - public Kind Kind { get; set; } = Kind.Single; - /// [YamlIgnore] public string? SourceRelativePath { get; set; } diff --git a/source/Models/IFrontMatter.cs b/source/Models/IFrontMatter.cs index 3618a4e19dc3a34e09ece3c26ce7f447c93987d8..29e704e2e30a4f4f5bfdcdc74c5c54c5629c6ea0 100644 --- a/source/Models/IFrontMatter.cs +++ b/source/Models/IFrontMatter.cs @@ -104,12 +104,6 @@ public interface IFrontMatter : IParams, IFile /// 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 date to be considered as the publishing date. /// diff --git a/source/Models/IPage.cs b/source/Models/IPage.cs index efe55cc41b4a7ab32294035641721acef217aac2..6de5c836545b9c3de75e767da8c60036fa6f190d 100644 --- a/source/Models/IPage.cs +++ b/source/Models/IPage.cs @@ -1,7 +1,7 @@ -using Markdig; -using SuCoS.Helpers; using System.Collections.Concurrent; using System.Collections.ObjectModel; +using Markdig; +using SuCoS.Helpers; namespace SuCoS.Models; @@ -10,8 +10,11 @@ namespace SuCoS.Models; /// public interface IPage : IFrontMatter, IOutput { - /// - new Kind Kind { get; set; } + /// + /// 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; set; } /// /// The source directory of the file. @@ -67,15 +70,10 @@ public interface IPage : IFrontMatter, IOutput /// 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; + public bool IsPage => (Kind & Kind.Single) == Kind.Single && (Kind & Kind.System) != Kind.System; /// /// The number of words in the main content diff --git a/source/Models/ISite.cs b/source/Models/ISite.cs index f2084ec11d2a2c8317c7374dba941e003209dea9..ba660cae4f25de93a596cdfc9dc85a9fa6c55b70 100644 --- a/source/Models/ISite.cs +++ b/source/Models/ISite.cs @@ -1,9 +1,9 @@ +using System.Collections.Concurrent; using Serilog; using SuCoS.Helpers; using SuCoS.Models.CommandLineOptions; using SuCoS.Parsers; using SuCoS.TemplateEngine; -using System.Collections.Concurrent; namespace SuCoS.Models; @@ -91,7 +91,11 @@ public interface ISite : ISiteSettings /// Folder recursive level /// Page of the upper directory /// - public void ParseAndScanSourceFiles(IFileSystem fs, string? directory, int level = 0, IPage? parent = null); + public void ParseAndScanSourceFiles( + IFileSystem fs, + string? directory, + int level = 0, + IPage? parent = null); /// /// Extra calculation and automatic data for each page. @@ -99,7 +103,10 @@ public interface ISite : ISiteSettings /// The given page to be processed /// The parent page, if any /// - public void PostProcessPage(in IPage page, IPage? parent = null, bool overwrite = false); + public void PostProcessPage( + in IPage page, + IPage? parent = null, + bool overwrite = false); /// /// Check if the page have the conditions to be published: valid date and not draft, @@ -108,7 +115,9 @@ public interface ISite : ISiteSettings /// Page or front matter /// options /// - public bool IsValidPage(in IFrontMatter frontMatter, IGenerateOptions? options); + public bool IsValidPage( + in IFrontMatter frontMatter, + IGenerateOptions? options); /// /// Check if the page have a publishing date from the past. @@ -116,7 +125,9 @@ public interface ISite : ISiteSettings /// Page or front matter /// options /// - public bool IsValidDate(in IFrontMatter frontMatter, IGenerateOptions? options); + public bool IsValidDate( + in IFrontMatter frontMatter, + IGenerateOptions? options); /// /// Check if the page is expired @@ -133,8 +144,12 @@ public interface ISite : ISiteSettings /// /// The relative path of the page. /// - /// + /// /// /// The created page for the index. - public IPage CreateSystemPage(string relativePath, string title, string? sectionName = null, IPage? originalPage = null); + public IPage CreateSystemPage( + string relativePath, + string title, + bool isTaxonomy = false, + IPage? originalPage = null); } diff --git a/source/Models/Kind.cs b/source/Models/Kind.cs index 99625fc853e7abf76118d9b5383e115a0f90bf98..a16a88dc2d95ec2b63d9a831daeae62813fe9cef 100644 --- a/source/Models/Kind.cs +++ b/source/Models/Kind.cs @@ -3,20 +3,51 @@ namespace SuCoS.Models; /// /// The type of the output page, if it's a single page, a list of pages or the home page. /// +[Flags] public enum Kind { /// /// A single content page. /// - Single, + Single = 1 << 1, /// /// List of contents /// - List, + List = 1 << 2, /// /// Special page, like the home page. It will be rendered as index.html. /// - Index + Index = 1 << 3, + + /// + /// Created by system + /// + System = 1 << 4, + + /// + /// Taxonomy type of content + /// + IsTaxonomy = 1 << 5, + + /// + /// Special page, like the home page. It will be rendered as index.html. + /// + Home = System | Index, + + /// + /// Root content list type + /// + Section = System | List, + + /// + /// Taxonomy group is equivalent to Section + /// + Taxonomy = System | IsTaxonomy | List, + + /// + /// Each taxonomy (category, tags) item + /// + Term = System | Single | IsTaxonomy | List, } diff --git a/source/Models/Page.cs b/source/Models/Page.cs index a11c1c4dc67be1594470b180c274623ec6763bc2..83f04f3c193e41fd6a4d7ddca86097b77dacf1f9 100644 --- a/source/Models/Page.cs +++ b/source/Models/Page.cs @@ -1,8 +1,8 @@ +using System.Collections.Concurrent; +using System.Collections.ObjectModel; using Markdig; using Microsoft.Extensions.FileSystemGlobbing; using SuCoS.Helpers; -using System.Collections.Concurrent; -using System.Collections.ObjectModel; namespace SuCoS.Models; @@ -62,11 +62,7 @@ public class Page : IPage public string RawContent => _frontMatter.RawContent; /// - public Kind Kind - { - get => _frontMatter.Kind; - set => (_frontMatter as FrontMatter)!.Kind = value; - } + public Kind Kind { get; set; } = Kind.Single; /// public string? SourceRelativePath => _frontMatter.SourceRelativePath; @@ -215,7 +211,7 @@ public class Page : IPage get { _regularPagesCache ??= Pages - .Where(page => page.Kind == Kind.Single) + .Where(page => page.IsPage) .ToList(); return _regularPagesCache; } @@ -354,12 +350,14 @@ endif } } + // TODO: remove the hard coded // Create tag pages, if any if (Tags is not null) { + Site.CreateSystemPage("tags", "Tags", true); foreach (var tagName in Tags) { - _ = Site.CreateSystemPage(Path.Combine("tags", tagName), tagName, "tags", this); + Site.CreateSystemPage(Path.Combine("tags", tagName), tagName, true, this); } } diff --git a/source/Models/Site.cs b/source/Models/Site.cs index ee19533d3c834ef4bc88f3b54481fdd75f4b66a0..0fdacafea590010a85e93a09cf733a2f83a14c5d 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -1,9 +1,9 @@ +using System.Collections.Concurrent; using Serilog; using SuCoS.Helpers; using SuCoS.Models.CommandLineOptions; using SuCoS.Parsers; using SuCoS.TemplateEngine; -using System.Collections.Concurrent; namespace SuCoS.Models; @@ -102,9 +102,9 @@ public class Site : ISite get { _regularPagesCache ??= OutputReferences - .Where(pair => pair.Value is IPage { IsPage: true } page && pair.Key == page.Permalink) - .Select(pair => (pair.Value as IPage)!) - .OrderBy(page => -page.Weight); + .Where(pair => pair.Value is IPage { IsPage: true } page && pair.Key == page.Permalink) + .Select(pair => (pair.Value as IPage)!) + .OrderBy(page => -page.Weight); return _regularPagesCache; } } @@ -209,46 +209,42 @@ public class Site : ISite }); } - /// - /// Creates the page for the site index. - /// - /// The relative path of the page. - /// - /// - /// - /// The created page for the index. - public IPage CreateSystemPage(string relativePath, string title, string? sectionName = null, IPage? originalPage = null) + /// + public IPage CreateSystemPage(string relativePath, string title, bool isTaxonomy = false, IPage? originalPage = null) { - sectionName ??= "section"; - var isIndex = string.IsNullOrEmpty(relativePath); relativePath = Urlizer.Path(relativePath); - FrontMatter frontMatter = new() - { - Kind = isIndex ? Kind.Index : Kind.List, - Section = isIndex ? "index" : sectionName, - SourceRelativePath = Urlizer.Path(Path.Combine(relativePath, IndexLeafFileConst)), - SourceFullPath = Urlizer.Path(Path.Combine(SourceContentPath, relativePath, IndexLeafFileConst)), - Title = title, - Type = isIndex ? "index" : sectionName, - URL = relativePath - }; + relativePath = relativePath == "homepage" ? "/" : relativePath; - var id = frontMatter.URL; + var id = relativePath; // Get or create the page var lazyPage = CacheManager.AutomaticContentCache.GetOrAdd(id, new Lazy(() => { - IPage? parent = null; - // Check if we need to create a section, even - var sections = (frontMatter.SourceRelativePathDirectory ?? string.Empty).Split('/', StringSplitOptions.RemoveEmptyEntries); - if (sections.Length > 1) + var directoryDepth = GetDirectoryDepth(relativePath); + var sectionName = GetFirstDirectory(relativePath); + var kind = directoryDepth switch { - parent = CreateSystemPage(sections[0], sections[0]); - } + 0 => Kind.Home, + 1 => isTaxonomy ? Kind.Taxonomy : Kind.Section, + _ => isTaxonomy ? Kind.Term : Kind.List + }; + + FrontMatter frontMatter = new() + { + Section = directoryDepth == 0 ? "index" : sectionName, + SourceRelativePath = Urlizer.Path(Path.Combine(relativePath, IndexLeafFileConst)), + SourceFullPath = Urlizer.Path(Path.Combine(SourceContentPath, relativePath, IndexLeafFileConst)), + Title = title, + Type = kind == Kind.Home ? "index" : sectionName, + URL = relativePath + }; + + IPage? parent = null; var newPage = new Page(frontMatter, this) { - BundleType = BundleType.Branch + BundleType = BundleType.Branch, + Kind = kind }; PostProcessPage(newPage, parent); return newPage; @@ -262,13 +258,13 @@ public class Site : ISite return page; } - if (page.Kind != Kind.Index) + if (page.Kind != Kind.Home) { page.PagesReferences.Add(originalPage.Permalink); } // TODO: still too hardcoded to add the tags reference - if (page.Type != "tags") + if ((page.Kind & Kind.IsTaxonomy) != Kind.IsTaxonomy) { return page; } @@ -276,6 +272,18 @@ public class Site : ISite return page; } + private static string GetFirstDirectory(string relativePath) => + GetDirectories(relativePath).Length > 0 + ? GetDirectories(relativePath)[0] + : string.Empty; + + private static int GetDirectoryDepth(string relativePath) => + GetDirectories(relativePath).Length; + + private static string[] GetDirectories(string? relativePath) => + (relativePath ?? string.Empty).Split('/', + StringSplitOptions.RemoveEmptyEntries); + private void ParseIndexPage(string? directory, int level, ref IPage? parent, ref string[] markdownFiles) { var indexLeafBundlePage = markdownFiles.FirstOrDefault(file => Path.GetFileName(file) == IndexLeafFileConst); @@ -302,7 +310,7 @@ public class Site : ISite { _ = OutputReferences.TryRemove(page.Permalink!, out _); page.Permalink = "/"; - page.Kind = Kind.Index; + page.Kind = Kind.Home; _ = OutputReferences.GetOrAdd(page.Permalink, page); Home = page; @@ -392,12 +400,12 @@ public class Site : ISite } if (!string.IsNullOrEmpty(page.Section) - && OutputReferences.TryGetValue('/' + page.Section!, out var output)) + && OutputReferences.TryGetValue('/' + page.Section!, out var output) + && (output is IPage section) + && page.Kind != Kind.Section + && page.Kind != Kind.Taxonomy) { - if (output is IPage section) - { - section.PagesReferences.Add(page.Permalink!); - } + section.PagesReferences.Add(page.Permalink!); } }