diff --git a/source/Commands/BaseGeneratorCommand.cs b/source/Commands/BaseGeneratorCommand.cs
index 254146cccf809fed80ebf99e16610febde059a03..6eba9d381f5ef77475f19804c6f46f67d5b8cc63 100644
--- a/source/Commands/BaseGeneratorCommand.cs
+++ b/source/Commands/BaseGeneratorCommand.cs
@@ -1,5 +1,3 @@
-using Fluid;
-using Fluid.Values;
using Serilog;
using SuCoS.Helpers;
using SuCoS.Models;
@@ -52,61 +50,6 @@ public abstract class BaseGeneratorCommand
logger.Information("Source path: {source}", propertyValue: options.Source);
- site = SiteHelper.Init(configFile, options, Parser, WhereParamsFilter, logger, stopwatch);
- }
-
- ///
- /// Fluid/Liquid filter to navigate Params dictionary
- ///
- ///
- ///
- ///
- ///
- ///
- protected static ValueTask WhereParamsFilter(FluidValue input, FilterArguments arguments, TemplateContext context)
- {
- ArgumentNullException.ThrowIfNull(input);
- ArgumentNullException.ThrowIfNull(arguments);
-
- List result = [];
- var list = (input as ArrayValue)!.Values;
-
- var keys = arguments.At(0).ToStringValue().Split('.');
- foreach (var item in list)
- {
- if (item.ToObjectValue() is IParams param && CheckValueInDictionary(keys, param.Params, arguments.At(1).ToStringValue()))
- {
- result.Add(item);
- }
- }
-
- return new ArrayValue(result);
- }
-
- private static bool CheckValueInDictionary(string[] array, IReadOnlyDictionary dictionary, string value)
- {
- var currentDictionary = dictionary;
- for (var i = 0; i < array.Length; i++)
- {
- var key = array[i];
-
- if (!currentDictionary.TryGetValue(key, out var dictionaryValue))
- {
- return false;
- }
-
- if (i == array.Length - 1)
- {
- return dictionaryValue.Equals(value);
- }
-
- if (dictionaryValue is not Dictionary nestedDictionary)
- {
- return false;
- }
-
- currentDictionary = nestedDictionary;
- }
- return false;
+ site = SiteHelper.Init(configFile, options, Parser, logger, stopwatch);
}
}
diff --git a/source/Commands/CheckLinkCommand.cs b/source/Commands/CheckLinkCommand.cs
index b8fd4c361ca135913329300c7665c7d0d864581b..2e9943053ffaa3aa06bb27354dcba748fd6472a2 100644
--- a/source/Commands/CheckLinkCommand.cs
+++ b/source/Commands/CheckLinkCommand.cs
@@ -223,4 +223,4 @@ public sealed partial class CheckLinkCommand(CheckLinkOptions settings, ILogger
logger.Error(message, fileName, link, arg);
}
}
-}
\ No newline at end of file
+}
diff --git a/source/Commands/ServeCommand.cs b/source/Commands/ServeCommand.cs
index 966593310831c7abf24bf2d826353f8f09cfceb0..fa6d7422031afa68c0c65cb6ef1af3899e85b79a 100644
--- a/source/Commands/ServeCommand.cs
+++ b/source/Commands/ServeCommand.cs
@@ -161,7 +161,7 @@ public sealed class ServeCommand : BaseGeneratorCommand, IDisposable
}
// Reinitialize the site
- site = SiteHelper.Init(configFile, options, Parser, WhereParamsFilter, logger, stopwatch);
+ site = SiteHelper.Init(configFile, options, Parser, logger, stopwatch);
StartServer(baseURLDefault, portDefault);
}).ConfigureAwait(false);
diff --git a/source/Helpers/SiteHelper.cs b/source/Helpers/SiteHelper.cs
index 2dcf23ede622249d91116d03b7ce2b139d94c873..5f0bcddfe0aeb1e3deadd5b381ca799f330aeea5 100644
--- a/source/Helpers/SiteHelper.cs
+++ b/source/Helpers/SiteHelper.cs
@@ -1,6 +1,4 @@
-using Fluid;
using Markdig;
-using Microsoft.Extensions.FileProviders;
using Serilog;
using SuCoS.Models;
using SuCoS.Models.CommandLineOptions;
@@ -25,7 +23,7 @@ public static class SiteHelper
/// Creates the pages dictionary.
///
///
- public static Site Init(string configFile, IGenerateOptions options, IMetadataParser parser, FilterDelegate whereParamsFilter, ILogger logger, StopwatchReporter stopwatch)
+ public static Site Init(string configFile, IGenerateOptions options, IMetadataParser parser, ILogger logger, StopwatchReporter stopwatch)
{
ArgumentNullException.ThrowIfNull(stopwatch);
@@ -41,10 +39,6 @@ public static class SiteHelper
var site = new Site(options, siteSettings, parser, 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");
@@ -55,7 +49,7 @@ public static class SiteHelper
if (Directory.Exists(Path.GetFullPath(site.SourceThemePath)))
{
- site.TemplateOptions.FileProvider = new PhysicalFileProvider(Path.GetFullPath(site.SourceThemePath));
+ site.TemplateEngine.Initialize(site);
}
return site;
diff --git a/source/Models/ISite.cs b/source/Models/ISite.cs
index fa40299be56c04e5030c9fab5a408c94d9ff8b09..40cab16bb5c6aae7c1288ac1fb846812e97b636a 100644
--- a/source/Models/ISite.cs
+++ b/source/Models/ISite.cs
@@ -1,8 +1,8 @@
-using Fluid;
using Serilog;
using SuCoS.Helpers;
using SuCoS.Models.CommandLineOptions;
using SuCoS.Parser;
+using SuCoS.TemplateEngine;
using System.Collections.Concurrent;
namespace SuCoS.Models;
@@ -63,14 +63,9 @@ public interface ISite : ISiteSettings, IParams
public IMetadataParser Parser { get; }
///
- /// The Fluid parser instance.
+ /// The template engine.
///
- public FluidParser FluidParser { get; }
-
- ///
- /// The Fluid/Liquid template options.
- ///
- public TemplateOptions TemplateOptions { get; }
+ public ITemplateEngine TemplateEngine { get; }
///
/// The logger instance.
@@ -136,4 +131,4 @@ public interface ISite : ISiteSettings, IParams
///
/// The created page for the index.
public IPage CreateSystemPage(string relativePath, string title, string? sectionName = null, IPage? originalPage = null);
-}
\ No newline at end of file
+}
diff --git a/source/Models/Page.cs b/source/Models/Page.cs
index cec47bfdfc377eb136ff52d2ac18e02129a8de9b..aaa16e5cd3d23f2f8e3217e29ca9849c866c5dd3 100644
--- a/source/Models/Page.cs
+++ b/source/Models/Page.cs
@@ -1,4 +1,3 @@
-using Fluid;
using Markdig;
using Microsoft.Extensions.FileSystemGlobbing;
using SuCoS.Helpers;
@@ -327,17 +326,7 @@ endif
try
{
- if (Site.FluidParser.TryParse(URLforce, out var template, out var error))
- {
- var context = new TemplateContext(Site.TemplateOptions)
- .SetValue("page", this)
- .SetValue("site", Site);
- permaLink = template.Render(context);
- }
- else
- {
- throw new FormatException(error);
- }
+ permaLink = Site.TemplateEngine.Parse(URLforce, Site, this);
}
catch (Exception ex)
{
@@ -433,22 +422,8 @@ endif
var file = new InMemoryDirectoryInfo("./", new[] { filenameOriginal });
if (resourceDefinition.GlobMatcher.Execute(file).HasMatches)
{
- if (Site.FluidParser.TryParse(resourceDefinition.Name, out var templateFileName, out var errorFileName))
- {
- var context = new TemplateContext(Site.TemplateOptions)
- .SetValue("page", this)
- .SetValue("site", Site)
- .SetValue("counter", counter);
- filename = templateFileName.Render(context);
- }
- if (Site.FluidParser.TryParse(resourceDefinition.Title, out var templateTitle, out var errorTitle))
- {
- var context = new TemplateContext(Site.TemplateOptions)
- .SetValue("page", this)
- .SetValue("site", Site)
- .SetValue("counter", counter);
- title = templateTitle.Render(context);
- }
+ filename = Site.TemplateEngine.ParseResource(resourceDefinition.Name, Site, this, counter) ?? filename;
+ title = Site.TemplateEngine.ParseResource(resourceDefinition.Title, Site, this, counter) ?? filename;
resourceParams = resourceDefinition.Params ?? [];
}
}
@@ -480,24 +455,14 @@ endif
return isBaseTemplate ? Content : ContentPreRendered;
}
- if (Site.FluidParser.TryParse(fileContents, out var template, out var error))
+ try
{
- var context = new TemplateContext(Site.TemplateOptions)
- .SetValue("page", this)
- .SetValue("site", Site);
- try
- {
- var rendered = template.Render(context);
- return rendered;
- }
- catch (Exception ex)
- {
- Site.Logger.Error(ex, errorMessage, error);
- return string.Empty;
- }
+ return Site.TemplateEngine.Parse(fileContents, Site, this);
+ }
+ catch (FormatException ex)
+ {
+ Site.Logger.Error(ex, errorMessage);
+ return string.Empty;
}
-
- Site.Logger.Error(errorMessage, error);
- return string.Empty;
}
}
diff --git a/source/Models/Site.cs b/source/Models/Site.cs
index 53128c2892f630d66a093c76dec72facccb31aea..67f58c3b86d462efdad3dc788d531190757ab01d 100644
--- a/source/Models/Site.cs
+++ b/source/Models/Site.cs
@@ -1,8 +1,8 @@
-using Fluid;
using Serilog;
using SuCoS.Helpers;
using SuCoS.Models.CommandLineOptions;
using SuCoS.Parser;
+using SuCoS.TemplateEngine;
using System.Collections.Concurrent;
namespace SuCoS.Models;
@@ -122,16 +122,6 @@ public class Site : ISite
///
public SiteCacheManager CacheManager { get; } = new();
- ///
- /// The Fluid parser instance.
- ///
- public FluidParser FluidParser { get; } = new();
-
- ///
- /// The Fluid/Liquid template options.
- ///
- public TemplateOptions TemplateOptions { get; } = new();
-
///
/// The logger instance.
///
@@ -167,6 +157,9 @@ public class Site : ISite
///
private readonly ISystemClock clock;
+ ///
+ public ITemplateEngine TemplateEngine { get; set; }
+
///
/// Constructor
///
@@ -180,13 +173,7 @@ public class Site : ISite
this.settings = settings;
Logger = logger;
Parser = frontMatterParser;
-
- // Liquid template options, needed to theme the content
- // but also parse URLs
- TemplateOptions.MemberAccessStrategy.Register();
- TemplateOptions.MemberAccessStrategy.Register();
- TemplateOptions.MemberAccessStrategy.Register();
- TemplateOptions.MemberAccessStrategy.Register();
+ TemplateEngine = new FluidTemplateEngine();
this.clock = clock ?? new SystemClock();
@@ -453,4 +440,4 @@ public class Site : ISite
return frontMatter.GetPublishDate is null || frontMatter.GetPublishDate <= clock.Now;
}
-}
\ No newline at end of file
+}
diff --git a/source/Models/SiteSettings.cs b/source/Models/SiteSettings.cs
index 796c0461deecbba2ffd954bbdac590b9232be742..e24f897e15879e4cabdb58dd4713b54a6858fff3 100644
--- a/source/Models/SiteSettings.cs
+++ b/source/Models/SiteSettings.cs
@@ -9,26 +9,25 @@ namespace SuCoS.Models;
public class SiteSettings : ISiteSettings
{
#region ISiteSettings
- ///
- /// Site Title/Name.
- ///
+
+ ///
public string Title { get; set; } = string.Empty;
- ///
- /// Site description
- ///
+
+ ///
public string? Description { get; set; } = string.Empty;
- ///
- /// Copyright information
- ///
+
+ ///
public string? Copyright { get; set; }
- ///
- /// The base URL that will be used to build public links.
- ///
+
+ ///
public string BaseURL { get; set; } = string.Empty;
+ ///
+ public Dictionary> Outputs { get; set; } = [];
+
#endregion ISiteSettings
///
diff --git a/source/SuCoS.csproj b/source/SuCoS.csproj
index 396eda4a5f35813900c8506ed07f386fb0724646..ea51196ad5c9ac3d977c070effc2d69c4272eacb 100644
--- a/source/SuCoS.csproj
+++ b/source/SuCoS.csproj
@@ -1,38 +1,38 @@
-
- Exe
- net8.0
- enable
- enable
- true
- false
- true
- true
- true
- Bruno Massa
- ../LICENSE
- hhttps://sucos.brunomassa.com
- static site generator;ssg;yaml/blog
-
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+ false
+ true
+ true
+ true
+ Bruno Massa
+ ../LICENSE
+ hhttps://sucos.brunomassa.com
+ static site generator;ssg;yaml/blog
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/source/TemplateEngine/FluidTemplateEngine.cs b/source/TemplateEngine/FluidTemplateEngine.cs
new file mode 100644
index 0000000000000000000000000000000000000000..800a2d5701f4eb3669976a0fb69208dd5432a036
--- /dev/null
+++ b/source/TemplateEngine/FluidTemplateEngine.cs
@@ -0,0 +1,132 @@
+using Fluid;
+using Fluid.Values;
+using Microsoft.Extensions.FileProviders;
+using SuCoS.Models;
+
+namespace SuCoS.TemplateEngine;
+
+///
+/// Fluid template engine
+///
+public class FluidTemplateEngine : ITemplateEngine
+{
+ ///
+ /// The Fluid parser instance.
+ ///
+ private FluidParser FluidParser { get; } = new();
+
+ ///
+ /// The Fluid/Liquid template options.
+ ///
+ private TemplateOptions TemplateOptions { get; } = new();
+
+ ///
+ /// ctor
+ ///
+ public FluidTemplateEngine()
+ {
+ // Liquid template options, needed to theme the content
+ // but also parse URLs
+ TemplateOptions.MemberAccessStrategy.Register();
+ TemplateOptions.MemberAccessStrategy.Register();
+ TemplateOptions.MemberAccessStrategy.Register();
+ TemplateOptions.MemberAccessStrategy.Register();
+
+ // Liquid template options, needed to theme the content
+ // but also parse URLs
+ TemplateOptions.Filters.AddFilter("whereParams", WhereParamsFilter);
+ }
+
+ ///
+ public void Initialize(Site site)
+ {
+ ArgumentNullException.ThrowIfNull(site);
+
+ TemplateOptions.FileProvider = new PhysicalFileProvider(Path.GetFullPath(site.SourceThemePath));
+ }
+
+ ///
+ public string Parse(string data, ISite site, IPage page)
+ {
+ if (FluidParser.TryParse(data, out var template, out var error))
+ {
+ var context = new TemplateContext(TemplateOptions)
+ .SetValue("site", site)
+ .SetValue("page", page);
+ return template.Render(context);
+ }
+ else
+ {
+ throw new FormatException(error);
+ }
+ }
+
+ ///
+ public string? ParseResource(string? data, ISite site, IPage page, int counter)
+ {
+ if (string.IsNullOrEmpty(data) || !FluidParser.TryParse(data, out var templateFileName, out var errorFileName))
+ {
+ return null;
+ }
+ var context = new TemplateContext(TemplateOptions)
+ .SetValue("site", site)
+ .SetValue("page", page)
+ .SetValue("counter", counter);
+ return templateFileName.Render(context);
+ }
+
+ ///
+ /// Fluid/Liquid filter to navigate Params dictionary
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected static ValueTask WhereParamsFilter(FluidValue input, FilterArguments arguments, TemplateContext context)
+ {
+ ArgumentNullException.ThrowIfNull(input);
+ ArgumentNullException.ThrowIfNull(arguments);
+
+ List result = [];
+ var list = (input as ArrayValue)!.Values;
+
+ var keys = arguments.At(0).ToStringValue().Split('.');
+ foreach (var item in list)
+ {
+ if (item.ToObjectValue() is IParams param && CheckValueInDictionary(keys, param.Params, arguments.At(1).ToStringValue()))
+ {
+ result.Add(item);
+ }
+ }
+
+ return new ArrayValue(result);
+ }
+
+ private static bool CheckValueInDictionary(string[] array, IReadOnlyDictionary dictionary, string value)
+ {
+ var currentDictionary = dictionary;
+ for (var i = 0; i < array.Length; i++)
+ {
+ var key = array[i];
+
+ if (!currentDictionary.TryGetValue(key, out var dictionaryValue))
+ {
+ return false;
+ }
+
+ if (i == array.Length - 1)
+ {
+ return dictionaryValue.Equals(value);
+ }
+
+ if (dictionaryValue is not Dictionary nestedDictionary)
+ {
+ return false;
+ }
+
+ currentDictionary = nestedDictionary;
+ }
+ return false;
+ }
+}
diff --git a/source/TemplateEngine/ITemplateEngine.cs b/source/TemplateEngine/ITemplateEngine.cs
new file mode 100644
index 0000000000000000000000000000000000000000..639ea2b766b702d92b4ff777091ffc2f05b0874f
--- /dev/null
+++ b/source/TemplateEngine/ITemplateEngine.cs
@@ -0,0 +1,34 @@
+using SuCoS.Models;
+
+namespace SuCoS.TemplateEngine;
+
+///
+/// Interface for all Templace Engines (Liquid)
+///
+public interface ITemplateEngine
+{
+ ///
+ /// Initialization
+ ///
+ ///
+ void Initialize(Site site);
+
+ ///
+ /// Parse the content using the data template
+ ///
+ ///
+ ///
+ ///
+ ///
+ string Parse(string template, ISite site, IPage page);
+
+ ///
+ /// Parse the content using the data template for Resource naming
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ string? ParseResource(string? template, ISite site, IPage page, int counter);
+}
diff --git a/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig b/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig
new file mode 100644
index 0000000000000000000000000000000000000000..0489b3488417452a4e0247f4c1ed7636c5706c04
--- /dev/null
+++ b/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig
@@ -0,0 +1,2 @@
+[*.cs]
+dotnet_diagnostic.CS1591.severity = none # disable on debug genereated files
diff --git a/test/BaseGeneratorCommandTests.cs b/test/BaseGeneratorCommandTests.cs
index de99b7aa479cde381438e4ccbd73dc1cec06471b..4aa4d7c513f878f8cf46e02e2f30a5b398029309 100644
--- a/test/BaseGeneratorCommandTests.cs
+++ b/test/BaseGeneratorCommandTests.cs
@@ -1,6 +1,7 @@
using Serilog;
using SuCoS;
using SuCoS.Models.CommandLineOptions;
+using SuCoS.TemplateEngine;
using System.Reflection;
using Xunit;
@@ -36,7 +37,7 @@ public class BaseGeneratorCommandTests
[Fact]
public void CheckValueInDictionary_ShouldWorkCorrectly()
{
- var type = typeof(BaseGeneratorCommand);
+ var type = typeof(FluidTemplateEngine);
var method = type.GetMethod("CheckValueInDictionary", BindingFlags.NonPublic | BindingFlags.Static);
var parameters = new object[] { new[] { "key" }, new Dictionary { { "key", "value" } }, "value" };