diff --git a/source/BaseGeneratorCommand.cs b/source/BaseGeneratorCommand.cs index 3a827765da5c75067d377c34f588272d8718eced..7d84c05cfb74fcae3a1f083c7040c59067e4b31f 100644 --- a/source/BaseGeneratorCommand.cs +++ b/source/BaseGeneratorCommand.cs @@ -17,6 +17,11 @@ namespace SuCoS; /// public abstract class BaseGeneratorCommand { + /// + /// Command line options + /// + readonly IGenerateOptions options; + /// /// The configuration file name. /// @@ -167,6 +172,7 @@ public abstract class BaseGeneratorCommand { throw new ArgumentNullException(nameof(options)); } + this.options = options; Log.Information("Source path: {source}", propertyValue: options.Source); @@ -228,12 +234,25 @@ public abstract class BaseGeneratorCommand try { var frontmatter = ReadSourceFrontmatter(file.filePath, file.content, site, frontmatterParser); - site.Pages.Add(frontmatter); - site.RegularPages.Add(frontmatter); + + if (IsValidDate(frontmatter)) + { + site.Pages.Add(frontmatter); + site.RegularPages.Add(frontmatter); + + // Convert the Markdown content to HTML + frontmatter.ContentPreRendered = Markdown.ToHtml(frontmatter.ContentRaw); + frontmatter.Permalink = "/" + GetOutputPath(file.filePath, site, frontmatter); + + if (site.HomePage is null && string.IsNullOrEmpty(frontmatter.SourcePath) && frontmatter.SourceFileNameWithoutExtension == "index") + { + site.HomePage = frontmatter; + } + } } - catch + catch (Exception ex) { - Log.Error("Error parsing file {file}", file.filePath); + Log.Error(ex, "Error parsing file {file}", file.filePath); } // Use interlocked to safely increment the counter in a multi-threaded environment @@ -359,18 +378,22 @@ public abstract class BaseGeneratorCommand var frontmatter = frontmatterParser.ParseFrontmatter(site, filePath, ref content, this) ?? throw new FormatException($"Error parsing frontmatter for {filePath}"); - // Convert the Markdown content to HTML - frontmatter.ContentPreRendered = Markdown.ToHtml(frontmatter.ContentRaw, markdownPipeline); - frontmatter.Permalink = "/" + GetOutputPath(filePath, site, frontmatter); - - if (site.HomePage is null && string.IsNullOrEmpty(frontmatter.SourcePath) && frontmatter.SourceFileNameWithoutExtension == "index") - { - site.HomePage = frontmatter; - } - return frontmatter; } + /// + /// Check if the page have a publishing date from the past. + /// + /// The given page + /// + bool IsValidDate(Frontmatter frontmatter) + { + return (frontmatter.PublishDate is null && frontmatter.Date is null) + || options.Future + || (frontmatter.PublishDate != null && (frontmatter.PublishDate <= DateTime.Now)) + || (frontmatter.Date != null && (frontmatter.Date <= DateTime.Now)); + } + /// /// Gets the output path for the file. /// diff --git a/source/BuildOptions.cs b/source/BuildOptions.cs index 11b073a966e220e7b475ebdbc6f5dd2e8ee71953..b1454b1fe994fc65da46781cf23501ba30a5aa61 100644 --- a/source/BuildOptions.cs +++ b/source/BuildOptions.cs @@ -5,18 +5,15 @@ namespace SuCoS; /// public class BuildOptions : IGenerateOptions { - /// - /// The path of the source files. - /// + /// public string Source { get; set; } = "."; - /// - /// The path of the output files. - /// + /// public string Output { get; set; } = "./public"; - /// - /// If true, the program will print more information. - /// + /// + public bool Future { get; set; } = false; + + /// public bool Verbose { get; set; } = false; } diff --git a/source/IGenerateOptions.cs b/source/IGenerateOptions.cs index f11cf04e2e71c2edeb319e3b3a0688128c050056..a89f3be7126dd5c5f1122fe4fd6b9f2e539ea722 100644 --- a/source/IGenerateOptions.cs +++ b/source/IGenerateOptions.cs @@ -15,6 +15,11 @@ public interface IGenerateOptions /// public string Output { get; set; } + /// + /// Consider + /// + bool Future { get; set; } + /// /// If true, the program will print more information. /// diff --git a/source/Models/Frontmatter.cs b/source/Models/Frontmatter.cs index aa5bebe9b587e48ab4fe9f54111bab29329387f3..323df8ebf9e9d80584c300bf1bd97ebdfe743491 100644 --- a/source/Models/Frontmatter.cs +++ b/source/Models/Frontmatter.cs @@ -16,6 +16,26 @@ public class Frontmatter /// public string Title { get; init; } + /// + /// 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. /// diff --git a/source/Parser/YAMLParser.cs b/source/Parser/YAMLParser.cs index 5ac8d4c5ecd03d70b1d93cefee9660252e4851c2..ae7453e11d00c7b30951bbe75c2a0399e431e825 100644 --- a/source/Parser/YAMLParser.cs +++ b/source/Parser/YAMLParser.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; +using Serilog; using SuCoS.Models; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -14,7 +16,7 @@ namespace SuCoS.Parser; public partial class YAMLParser : IFrontmatterParser { [GeneratedRegex(@"^---\s*[\r\n](?.*?)[\r\n]---\s*", RegexOptions.Singleline)] - private partial Regex _regex(); + private partial Regex regex(); /// public Frontmatter? ParseFrontmatter(Site site, string filePath, ref string fileContent, BaseGeneratorCommand frontmatterManager) @@ -25,7 +27,7 @@ public partial class YAMLParser : IFrontmatterParser } Frontmatter? frontmatter = null; - var match = _regex().Match(fileContent); + var match = regex().Match(fileContent); if (match.Success) { var frontmatterString = match.Groups["frontmatter"].Value; @@ -41,6 +43,10 @@ public partial class YAMLParser : IFrontmatterParser _ = frontmatterDictionary.TryGetValue("Title", out var titleValue); _ = frontmatterDictionary.TryGetValue("URL", out var urlValue); _ = frontmatterDictionary.TryGetValue("Type", out var typeValue); + _ = frontmatterDictionary.TryGetValue("Date", out var dateValue); + _ = frontmatterDictionary.TryGetValue("LastMod", out var dateLastModValue); + _ = frontmatterDictionary.TryGetValue("PublishDate", out var datePublishValue); + _ = frontmatterDictionary.TryGetValue("ExpiryDate", out var dateExpiryValue); var section = GetSection(site, filePath); List tags = new(); @@ -69,7 +75,11 @@ public partial class YAMLParser : IFrontmatterParser URL = urlValue?.ToString(), Section = section, Type = typeValue?.ToString() ?? section, - Kind = Kind.single + Kind = Kind.single, + Date = DateTime.TryParse(dateValue?.ToString(), out var parsedDate) ? parsedDate : null, + LastMod = DateTime.TryParse(dateLastModValue?.ToString(), out var parsedLastMod) ? parsedLastMod : null, + PublishDate = DateTime.TryParse(datePublishValue?.ToString(), out var parsedPublishDate) ? parsedPublishDate : null, + ExpiryDate = DateTime.TryParse(dateExpiryValue?.ToString(), out var parsedExpiryDate) ? parsedExpiryDate : null }; foreach (var tagName in tags) diff --git a/source/Program.cs b/source/Program.cs index 0dafccdcb1542b7b99b0576e36b1fe32eb526ca0..8e3266effc07ce7d1ab17c0333fa03081dd581bf 100644 --- a/source/Program.cs +++ b/source/Program.cs @@ -30,40 +30,45 @@ public class Program // Shared options between the commands var sourceOption = new Option(new[] { "--source", "-s" }, () => ".", "Source directory path"); + var futureOption = new Option(new[] { "--future", "-f" }, () => false, "Include content with dates in the future"); var verboseOption = new Option(new[] { "--verbose", "-v" }, () => false, "Verbose output"); // BuildCommand setup var buildOutputOption = new Option(new[] { "--output", "-o" }, () => "./public", "Output directory path"); Command buildCommand = new("build", "Builds the site") - { - sourceOption, - buildOutputOption, - verboseOption - }; - buildCommand.SetHandler((source, output, verbose) => + { + sourceOption, + buildOutputOption, + futureOption, + verboseOption + }; + buildCommand.SetHandler((source, output, future, verbose) => { BuildOptions buildOptions = new() { Source = source, Output = output, + Future = future, Verbose = verbose }; _ = new BuildCommand(buildOptions); }, - sourceOption, buildOutputOption, verboseOption); + sourceOption, buildOutputOption, futureOption, verboseOption); // ServerCommand setup Command serveCommand = new("serve", "Starts the server") { sourceOption, + futureOption, verboseOption }; - serveCommand.SetHandler(async (source, verbose) => + serveCommand.SetHandler(async (source, future, verbose) => { ServeOptions serverOptions = new() { Source = source, + Future = future, Verbose = verbose }; @@ -71,7 +76,7 @@ public class Program await serveCommand.RunServer(); await Task.Delay(-1); // Wait forever. }, - sourceOption, verboseOption); + sourceOption, futureOption, verboseOption); RootCommand rootCommand = new("SuCoS commands") { diff --git a/source/ServeOptions.cs b/source/ServeOptions.cs index b0a339acd39d3b31be719057239d617051de0720..d393250f351815a9c8c5b89679938a2d2e35269e 100644 --- a/source/ServeOptions.cs +++ b/source/ServeOptions.cs @@ -5,18 +5,15 @@ namespace SuCoS; /// public class ServeOptions : IGenerateOptions { - /// - /// The path of the source files. - /// + /// public string Source { get; set; } = "."; - /// - /// The path of the output files. - /// + /// public string Output { get; set; } = "./public"; - /// - /// If true, the program will print more information. - /// + /// + public bool Future { get; set; } = false; + + /// public bool Verbose { get; set; } = false; }