diff --git a/source/Helpers/FileUtils.cs b/source/Helpers/FileUtils.cs
index 3fbf30f45bd4b6f4af0d7fd61a427515173c3f73..963b662eca34de27caf83bd1864b4c2539705725 100644
--- a/source/Helpers/FileUtils.cs
+++ b/source/Helpers/FileUtils.cs
@@ -11,26 +11,6 @@ namespace SuCoS.Helpers;
///
public static class FileUtils
{
- ///
- /// Gets all Markdown files in the specified directory.
- ///
- /// The directory path.
- /// The initial directory path.
- /// The list of Markdown file paths.
- public static IEnumerable GetAllMarkdownFiles(string directory, string? basePath = null)
- {
- basePath ??= directory;
- var files = Directory.GetFiles(directory, "*.md").ToList();
-
- var subdirectories = Directory.GetDirectories(directory);
- foreach (var subdirectory in subdirectories)
- {
- files.AddRange(GetAllMarkdownFiles(subdirectory, basePath));
- }
-
- return files;
- }
-
///
/// Gets the content of a template file based on the frontmatter and the theme path.
///
diff --git a/source/Helpers/SiteHelper.cs b/source/Helpers/SiteHelper.cs
index 0207a5f4381bd57a8ab55986089a97b66d09774b..5dcc818b3161b6903aeb750c653483fea6443f8b 100644
--- a/source/Helpers/SiteHelper.cs
+++ b/source/Helpers/SiteHelper.cs
@@ -36,11 +36,11 @@ public static class SiteHelper
site.ResetCache();
- // Scan content files
- var markdownFiles = FileUtils.GetAllMarkdownFiles(site.SourceContentPath);
- site.ContentPaths.AddRange(markdownFiles);
+ stopwatch.Start("Parse");
- site.ParseSourceFiles(stopwatch);
+ site.ParseAndScanSourceFiles(site.SourceContentPath);
+
+ stopwatch.Stop("Parse", site.filesParsedToReport);
site.TemplateOptions.FileProvider = new PhysicalFileProvider(Path.GetFullPath(site.SourceThemePath));
@@ -109,7 +109,7 @@ public static class SiteHelper
site.Logger = logger;
site.options = options;
site.SourceDirectoryPath = options.Source;
- site.OutputPath = options.Output;
+ site.OutputPath = options.Output!;
// Liquid template options, needed to theme the content
// but also parse URLs
diff --git a/source/Models/Frontmatter.cs b/source/Models/Frontmatter.cs
index 621f4c2a80e91a7dc1695a6029fb58535fe2c8c6..378e3782f374bd3b29e11ba048949f26d86d14fd 100644
--- a/source/Models/Frontmatter.cs
+++ b/source/Models/Frontmatter.cs
@@ -89,6 +89,12 @@ public class Frontmatter : IBaseContent, IParams
[YamlIgnore]
public string? SourcePathDirectory { get; set; }
+ ///
+ /// The source directory of the file.
+ ///
+ [YamlIgnore]
+ public string? SourcePathLastDirectory => Path.GetDirectoryName(SourcePathDirectory ?? string.Empty);
+
///
/// Point to the site configuration.
///
@@ -120,6 +126,13 @@ public class Frontmatter : IBaseContent, IParams
[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; }
+
///
/// A list of tags, if any.
///
@@ -315,8 +328,22 @@ public class Frontmatter : IBaseContent, IParams
URLforce ??= URL
?? (isIndex
- ? "{{ page.SourcePathDirectory }}"
- : @"{{ page.SourcePathDirectory }}/{%- liquid
+ ? @"{%- liquid
+if page.Parent
+echo page.Parent.Permalink
+echo '/'
+endif
+if page.Title != ''
+echo page.Title
+else
+echo page.SourcePathLastDirectory
+endif
+-%}"
+ : @"{%- liquid
+if page.Parent
+echo page.Parent.Permalink
+echo '/'
+endif
if page.Title != ''
echo page.Title
else
diff --git a/source/Models/Site.cs b/source/Models/Site.cs
index a3159fbbac33db9e2a2a580160e5caedc9c14a39..da1b57691bde36875dabdb2b287c5281e9f7b73e 100644
--- a/source/Models/Site.cs
+++ b/source/Models/Site.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -7,7 +8,6 @@ using System.Threading.Tasks;
using Fluid;
using Markdig;
using Serilog;
-using SuCoS.Helpers;
using SuCoS.Parser;
using YamlDotNet.Serialization;
@@ -119,12 +119,6 @@ public class Site : IParams
///
public Frontmatter? Home { get; private set; }
- ///
- /// List of all content to be scanned and processed.
- ///
- [YamlIgnore]
- public List ContentPaths { get; } = new();
-
///
/// Command line options
///
@@ -189,6 +183,11 @@ public class Site : IParams
private List? regularPagesCache;
+ ///
+ /// Number of files parsed, used in the report.
+ ///
+ public int filesParsedToReport;
+
///
/// Markdig 20+ built-in extensions
///
@@ -229,20 +228,99 @@ public class Site : IParams
IgnoreCacheBefore = DateTime.Now;
}
+ ///
+ /// Search recursively for all markdown files in the content folder, then
+ /// parse their content for front matter meta data and markdown.
+ ///
+ /// Folder to scan
+ /// Folder recursive level
+ /// Page of the upper directory
+ ///
+ public void ParseAndScanSourceFiles(string directory, int level = 0, Frontmatter? pageParent = null)
+ {
+ directory ??= SourceContentPath;
+
+ var markdownFiles = Directory.GetFiles(directory, "*.md");
+
+ var indexPath = markdownFiles.FirstOrDefault(file => Path.GetFileName(file).ToUpperInvariant() == "INDEX.MD");
+ if (indexPath != null)
+ {
+ markdownFiles = markdownFiles.Where(file => file != indexPath).ToArray();
+ var frontmatter = ParseSourceFile(pageParent, indexPath);
+ if (level == 0)
+ {
+ Home = frontmatter;
+ frontmatter!.Permalink = "/";
+ PagesDict.Remove(frontmatter.Permalink);
+ PagesDict.Add(frontmatter.Permalink, frontmatter);
+ }
+ else
+ {
+ pageParent = frontmatter;
+ }
+ }
+ else if (level == 0)
+ {
+ Home = CreateIndexPage(string.Empty);
+ }
+ else if (level == 1)
+ {
+ var section = directory;
+ var contentTemplate = new BasicContent(
+ title: section,
+ section: "section",
+ type: "section",
+ url: section
+ );
+ pageParent = CreateAutomaticFrontmatter(contentTemplate, null);
+ }
+
+ _ = Parallel.ForEach(markdownFiles, filePath =>
+ {
+ ParseSourceFile(pageParent, filePath);
+ });
+
+ var subdirectories = Directory.GetDirectories(directory);
+ foreach (var subdirectory in subdirectories)
+ {
+ ParseAndScanSourceFiles(subdirectory, level + 1, pageParent);
+ }
+ }
+
+ private Frontmatter? ParseSourceFile(Frontmatter? pageParent, string filePath)
+ {
+ Frontmatter? frontmatter = null;
+ try
+ {
+ frontmatter = frontmatterParser.ParseFrontmatterAndMarkdownFromFile(this, filePath, SourceContentPath)
+ ?? throw new FormatException($"Error parsing frontmatter for {filePath}");
+
+ if (frontmatter.IsValidDate(options))
+ {
+ PostProcessFrontMatter(frontmatter, pageParent, true);
+ }
+ }
+ catch (Exception ex)
+ {
+ 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;
+ }
+
///
/// Create a page not from the content folder, but as part of the process.
/// It's used to create tag pages, section list pages, etc.
///
- public Frontmatter CreateAutomaticFrontmatter(BasicContent baseContent, Frontmatter originalFrontmatter)
+ public Frontmatter CreateAutomaticFrontmatter(BasicContent baseContent, Frontmatter? originalFrontmatter)
{
if (baseContent is null)
{
throw new ArgumentNullException(nameof(baseContent));
}
- if (originalFrontmatter is null)
- {
- throw new ArgumentNullException(nameof(originalFrontmatter));
- }
var id = baseContent.URL;
Frontmatter? frontmatter;
@@ -270,15 +348,17 @@ public class Site : IParams
}
}
- if (frontmatter.Kind != Kind.index && originalFrontmatter.Permalink is not null)
+ if (frontmatter.Kind != Kind.index && originalFrontmatter?.Permalink is not null)
{
frontmatter.PagesReferences!.Add(originalFrontmatter.Permalink!);
}
+
// TODO: still too hardcoded
- if (frontmatter.Type != "tags")
+ if (frontmatter.Type != "tags" || originalFrontmatter is null)
+ {
return frontmatter;
-
+ }
lock (originalFrontmatter)
{
originalFrontmatter.Tags ??= new();
@@ -287,52 +367,6 @@ public class Site : IParams
return frontmatter;
}
- ///
- /// Parses the source files and extracts the frontmatter.
- ///
- public void ParseSourceFiles(StopwatchReporter stopwatch)
- {
- if (stopwatch is null)
- {
- throw new ArgumentNullException(nameof(stopwatch));
- }
-
- stopwatch.Start("Parse");
-
- // Process the source files, extracting the frontmatter
- var filesParsed = 0; // counter to keep track of the number of files processed
- _ = Parallel.ForEach(ContentPaths, filePath =>
- {
- try
- {
- var frontmatter = frontmatterParser.ParseFrontmatterAndMarkdownFromFile(this, filePath, SourceContentPath)
- ?? throw new FormatException($"Error parsing frontmatter for {filePath}");
-
- if (frontmatter.IsValidDate(options))
- {
- PostProcessFrontMatter(frontmatter, true);
- }
- }
- catch (Exception ex)
- {
- Logger?.Error(ex, "Error parsing file {file}", filePath);
- }
-
- // Use interlocked to safely increment the counter in a multi-threaded environment
- _ = Interlocked.Increment(ref filesParsed);
- });
-
- // If the home page is not yet created, create it!
- if (!PagesDict.TryGetValue("/", out var home))
- {
- home = CreateIndexPage(string.Empty);
- }
- Home = home;
- home.Kind = Kind.index;
-
- stopwatch.Stop("Parse", filesParsed);
- }
-
///
/// Creates the frontmatter for the index page.
///
@@ -359,15 +393,17 @@ public class Site : IParams
///
/// Extra calculation and automatic data for each frontmatter.
///
- ///
+ /// The given page to be processed
+ /// The parent page, if any
///
- public void PostProcessFrontMatter(Frontmatter frontmatter, bool overwrite = false)
+ public void PostProcessFrontMatter(Frontmatter frontmatter, Frontmatter? pageParent = null, bool overwrite = false)
{
if (frontmatter is null)
{
throw new ArgumentNullException(nameof(frontmatter));
}
+ frontmatter.Parent = pageParent;
frontmatter.Permalink = frontmatter.CreatePermalink();
lock (syncLockPostProcess)
{
@@ -398,20 +434,5 @@ public class Site : IParams
}
}
}
-
- // Create a section page when due
- if (frontmatter.Type == "section"
- || string.IsNullOrEmpty(frontmatter.Permalink)
- || string.IsNullOrEmpty(frontmatter.Section))
- {
- return;
- }
- var contentTemplate = new BasicContent(
- title: frontmatter.Section,
- section: "section",
- type: "section",
- url: frontmatter.Section
- );
- CreateAutomaticFrontmatter(contentTemplate, frontmatter);
}
-}
+}
\ No newline at end of file
diff --git a/test/Models/FrontmatterTests.cs b/test/Models/FrontmatterTests.cs
index d94e08b1dad1a8298ab59b11eee285867e18b4d4..b6dcb78c2305098cf2034c19f376e58888089546 100644
--- a/test/Models/FrontmatterTests.cs
+++ b/test/Models/FrontmatterTests.cs
@@ -135,8 +135,8 @@ public class FrontmatterTests
}
[Theory]
- [InlineData("/test/path", "/test/path/test-title")]
- [InlineData("/another/path", "/another/path/test-title")]
+ [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)
@@ -149,7 +149,7 @@ public class FrontmatterTests
}
[Theory]
- [InlineData(null, "/path/to/test-title")]
+ [InlineData(null, "/test-title")]
[InlineData("{{ page.Title }}/{{ page.SourceFileNameWithoutExtension }}", "/test-title/file")]
public void Permalink_CreateWithDefaultOrCustomURLTemplate(string urlTemplate, string expectedPermalink)
{
diff --git a/test/Models/SiteTests.cs b/test/Models/SiteTests.cs
index da57db3b71a331bd52ac926a57840449b74721db..f5364b815589b7473e728c7933b739b92d45a4ad 100644
--- a/test/Models/SiteTests.cs
+++ b/test/Models/SiteTests.cs
@@ -31,14 +31,15 @@ public class SiteTests
[InlineData("test02.md")]
public void Test_ScanAllMarkdownFiles(string fileName)
{
+ var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
var siteFullPath = Path.GetFullPath(Path.Combine(testSitesPath, testSite1PathCONST));
// Act
- var ContentPaths = FileUtils.GetAllMarkdownFiles(Path.Combine(siteFullPath, "content"));
- var fileFullPath = Path.Combine(siteFullPath, "content", fileName);
+ site.ParseAndScanSourceFiles(siteFullPath);
// Assert
- Assert.Contains(ContentPaths, rp => rp == fileFullPath);
+ Assert.Contains(site.Pages, page => page.SourcePathDirectory?.Length == 0);
+ Assert.Contains(site.Pages, page => page.SourceFileNameWithoutExtension == fileNameWithoutExtension);
}
[Theory]