diff --git a/source/Commands/NewSiteCommand.cs b/source/Commands/NewSiteCommand.cs
index e2010712b21043a430b2aabf1cc2e2696c0e1cd5..3b9ae662ea27fe8f99e09afb83ef7eb89e9e6f2e 100644
--- a/source/Commands/NewSiteCommand.cs
+++ b/source/Commands/NewSiteCommand.cs
@@ -1,45 +1,59 @@
using Serilog;
using SuCoS.Models;
using SuCoS.Models.CommandLineOptions;
+using SuCoS.Parser;
namespace SuCoS;
///
/// Check links of a given site.
///
-public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logger)
+public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logger, IFileSystem fileSystem, ISite site)
{
+ private static SiteSettings siteSettings = null!;
+
///
- /// Run the app
+ /// Generate the needed data for the class
///
+ ///
+ ///
+ ///
///
- public int Run()
+ public static NewSiteCommand Create(NewSiteOptions options, ILogger logger, IFileSystem fileSystem)
{
- var siteSettings = new SiteSettings()
+ ArgumentNullException.ThrowIfNull(options);
+
+ siteSettings = new SiteSettings()
{
Title = options.Title,
Description = options.Description,
BaseURL = options.BaseURL
};
- // TODO: Refactor Site class to not need YAML parser nor FrontMatterParser
- var site = new Site(new ServeOptions() { SourceOption = options.Output }, siteSettings, null!, logger, null);
+ var site = new Site(new GenerateOptions() { SourceOption = options.Output }, siteSettings, new YAMLParser(), null!, null);
+ return new NewSiteCommand(options, logger, fileSystem, site);
+ }
- var outputPath = Path.GetFullPath(options.Output);
- var siteSettingsPath = Path.Combine(outputPath, "sucos.yaml");
+ ///
+ /// Run the app
+ ///
+ ///
+ public int Run()
+ {
+ var outputPath = fileSystem.GetFullPath(options.Output);
+ var siteSettingsPath = fileSystem.Combine(outputPath, "sucos.yaml");
- if (File.Exists(siteSettingsPath) && !options.Force)
+ if (fileSystem.FileExists(siteSettingsPath) && !options.Force)
{
logger.Error("{directoryPath} already exists", outputPath);
return 1;
}
- logger.Information("Creating a new site: {title} at {outputPath}", siteSettings.Title, outputPath);
-
- CreateFolders(site.SourceFodlers);
+ logger.Information("Creating a new site: {title} at {outputPath}", site.Title, outputPath);
try
{
+ CreateFolders(site.SourceFolders);
site.Parser.Export(siteSettings, siteSettingsPath);
}
catch (Exception ex)
@@ -61,7 +75,7 @@ public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logge
foreach (var folder in folders)
{
logger.Information("Creating {folder}", folder);
- Directory.CreateDirectory(folder);
+ fileSystem.CreateDirectory(folder);
}
}
}
\ No newline at end of file
diff --git a/source/Helpers/IFileSystem.cs b/source/Helpers/IFileSystem.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b6e5eeb531d5d6b2b09dae02e84cef47572ba6ac
--- /dev/null
+++ b/source/Helpers/IFileSystem.cs
@@ -0,0 +1,53 @@
+namespace SuCoS;
+
+///
+/// Interface for the System.File class
+///
+public interface IFileSystem
+{
+ ///
+ /// Path.GetFullPath
+ ///
+ ///
+ ///
+ string GetFullPath(string path);
+
+ ///
+ /// Path.Combine
+ ///
+ ///
+ ///
+ ///
+ string Combine(string path1, string path2);
+
+ ///
+ /// File.Exists
+ ///
+ ///
+ ///
+ bool FileExists(string path);
+
+ ///
+ /// Directory.CreateDirectory
+ ///
+ ///
+ void CreateDirectory(string path);
+}
+
+///
+/// Actual implementation of FS operations
+///
+public class FileSystem : IFileSystem
+{
+ ///
+ public string GetFullPath(string path) => Path.GetFullPath(path);
+
+ ///
+ public string Combine(string path1, string path2) => Path.Combine(path1, path2);
+
+ ///
+ public bool FileExists(string path) => File.Exists(path);
+
+ ///
+ public void CreateDirectory(string path) => Directory.CreateDirectory(path);
+}
diff --git a/source/Models/CommandLineOptions/NewSiteOptions.cs b/source/Models/CommandLineOptions/NewSiteOptions.cs
index f0dc375fb9232c26ce8d7c6989d0ec5895288704..6584a1ef0ac61bd22c52c2a75650ab4673d63d5d 100644
--- a/source/Models/CommandLineOptions/NewSiteOptions.cs
+++ b/source/Models/CommandLineOptions/NewSiteOptions.cs
@@ -24,17 +24,17 @@ public class NewSiteOptions
/// Site title.
///
[Option("title", Required = false, HelpText = "Site title")]
- public required string Title { get; init; } = "My Site";
+ public string Title { get; init; } = "My Site";
///
/// Site description.
///
[Option("description", Required = false, HelpText = "Site description")]
- public required string Description { get; init; } = string.Empty;
+ public string Description { get; init; } = string.Empty;
///
/// Site base url.
///
[Option("url", Required = false, HelpText = "Site base url")]
- public required string BaseURL { get; init; } = "https://example.org/";
+ public string BaseURL { get; init; } = "https://example.org/";
}
diff --git a/source/Models/ISite.cs b/source/Models/ISite.cs
index 40cab16bb5c6aae7c1288ac1fb846812e97b636a..cdbc88be69429de864f4948ce585904660c9cc38 100644
--- a/source/Models/ISite.cs
+++ b/source/Models/ISite.cs
@@ -72,6 +72,11 @@ public interface ISite : ISiteSettings, IParams
///
public ILogger Logger { get; }
+ ///
+ /// List of all basic source folders
+ ///
+ public IEnumerable SourceFolders { get; }
+
///
/// Resets the template cache to force a reload of all templates.
///
diff --git a/source/Models/Site.cs b/source/Models/Site.cs
index a70d290c117b88139ae321df3c5904adb471bb50..c315c2b70a55200431dda55f2ecf099425bcefd0 100644
--- a/source/Models/Site.cs
+++ b/source/Models/Site.cs
@@ -1,4 +1,3 @@
-using CommandLine;
using Serilog;
using SuCoS.Helpers;
using SuCoS.Models.CommandLineOptions;
@@ -64,10 +63,8 @@ public class Site : ISite
///
public string SourceThemePath => Path.Combine(Options.Source, settings.ThemeDir, settings.Theme ?? string.Empty);
- ///
- /// List of all basic source folders
- ///
- public IEnumerable SourceFodlers => [
+ ///
+ public IEnumerable SourceFolders => [
SourceContentPath,
SourceStaticPath,
SourceThemePath
@@ -167,13 +164,13 @@ public class Site : ISite
public Site(
in IGenerateOptions options,
in SiteSettings settings,
- in IMetadataParser frontMatterParser,
+ in IMetadataParser parser,
in ILogger logger, ISystemClock? clock)
{
Options = options;
this.settings = settings;
Logger = logger;
- Parser = frontMatterParser;
+ Parser = parser;
TemplateEngine = new FluidTemplateEngine();
this.clock = clock ?? new SystemClock();
diff --git a/source/Program.cs b/source/Program.cs
index d99daa7148f2ed68744affc87850641220f27d57..c6cf3f078d7443a21c5bfa7c14ad11bca3230cb6 100644
--- a/source/Program.cs
+++ b/source/Program.cs
@@ -103,7 +103,8 @@ public class Program(ILogger logger)
},
(NewSiteOptions options) =>
{
- var command = new NewSiteCommand(options, logger);
+ var fileSystem = new FileSystem();
+ var command = NewSiteCommand.Create(options, logger, fileSystem);
return Task.FromResult(command.Run());
},
(NewThemeOptions options) =>
diff --git a/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig b/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig
deleted file mode 100644
index 0489b3488417452a4e0247f4c1ed7636c5706c04..0000000000000000000000000000000000000000
--- a/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-[*.cs]
-dotnet_diagnostic.CS1591.severity = none # disable on debug genereated files
diff --git a/test/BaseGeneratorCommandTests.cs b/test/Commands/BaseGeneratorCommandTests.cs
similarity index 86%
rename from test/BaseGeneratorCommandTests.cs
rename to test/Commands/BaseGeneratorCommandTests.cs
index 4aa4d7c513f878f8cf46e02e2f30a5b398029309..28040d2a418ba264bdbb6e0f0a62b3d63465d30d 100644
--- a/test/BaseGeneratorCommandTests.cs
+++ b/test/Commands/BaseGeneratorCommandTests.cs
@@ -5,7 +5,7 @@ using SuCoS.TemplateEngine;
using System.Reflection;
using Xunit;
-namespace Tests;
+namespace Tests.Commands;
public class BaseGeneratorCommandTests
{
@@ -16,11 +16,8 @@ public class BaseGeneratorCommandTests
private static readonly ILogger testLogger = new LoggerConfiguration().CreateLogger();
- private class BaseGeneratorCommandStub : BaseGeneratorCommand
- {
- public BaseGeneratorCommandStub(IGenerateOptions options, ILogger logger)
- : base(options, logger) { }
- }
+ private class BaseGeneratorCommandStub(IGenerateOptions options, ILogger logger)
+ : BaseGeneratorCommand(options, logger);
[Fact]
public void Constructor_ShouldThrowArgumentNullException_WhenOptionsIsNull()
diff --git a/test/Commands/NewSiteCommandTests.cs b/test/Commands/NewSiteCommandTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..af1c3712911aea8620fa4b2068347669e61af4a4
--- /dev/null
+++ b/test/Commands/NewSiteCommandTests.cs
@@ -0,0 +1,194 @@
+using SuCoS;
+using SuCoS.Models;
+using SuCoS.Models.CommandLineOptions;
+using Xunit;
+using NSubstitute;
+using Serilog;
+
+namespace Tests.Commands;
+
+public class NewSiteCommandTests
+{
+ readonly ILogger logger;
+ readonly IFileSystem fileSystem;
+ readonly ISite site;
+
+ public NewSiteCommandTests()
+ {
+ logger = Substitute.For();
+ fileSystem = Substitute.For();
+ site = Substitute.For();
+ }
+
+ [Fact]
+ public void Create_ShouldReturnNewSiteCommand_WhenParametersAreValid()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com" };
+
+ // Act
+ var result = NewSiteCommand.Create(options, logger, fileSystem);
+
+ // Assert
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void Create_ShouldThrowArgumentNullException_WhenOptionsIsNull()
+ {
+ // Act and Assert
+ Assert.Throws(testCode: () => NewSiteCommand.Create(null!, logger, fileSystem));
+ }
+
+ [Fact]
+ public void Create_ShouldNotReturnNull_WhenParametersAreValid()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com" };
+
+ // Act
+ var result = NewSiteCommand.Create(options, logger, fileSystem);
+
+ // Assert
+ Assert.NotNull(result);
+ }
+
+ [Fact]
+ public void Run_ShouldLogInformation_WhenCreatingNewSite()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false };
+ fileSystem.FileExists(Arg.Any()).Returns(false);
+
+ var newSiteCommand = NewSiteCommand.Create(options, logger, fileSystem);
+
+ // Act
+ newSiteCommand.Run();
+
+ // Assert
+ logger.Received(1).Information("Creating a new site: {title} at {outputPath}", options.Title, Arg.Any());
+ }
+
+ [Fact]
+ public void Run_ShouldCallCreateDirectoryWithCorrectPaths_ForEachFolder()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false };
+ var site = Substitute.For();
+ site.SourceFolders.Returns(["folder1", "folder2"]);
+ fileSystem.FileExists(Arg.Any()).Returns(false);
+
+ var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site);
+
+ // Act
+ newSiteCommand.Run();
+
+ // Assert
+ fileSystem.Received(1).CreateDirectory("folder1");
+ fileSystem.Received(1).CreateDirectory("folder2");
+ }
+
+ [Fact]
+ public void Run_ShouldReturn1_WhenCreateDirectoryThrowsException()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false };
+ var site = Substitute.For();
+ site.SourceFolders.Returns(["folder1", "folder2"]);
+ fileSystem.FileExists(Arg.Any()).Returns(false);
+ fileSystem.When(x => x.CreateDirectory(Arg.Any()))
+ .Do(x => { throw new ArgumentNullException(); });
+
+ var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site);
+
+ // Act
+ var result = newSiteCommand.Run();
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void Run_ShouldReturn0_WhenDirectoryDoesNotExist()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Force = false };
+ fileSystem.FileExists(Arg.Any()).Returns(false);
+
+ var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site);
+
+ // Act
+ var result = newSiteCommand.Run();
+
+ // Assert
+ Assert.Equal(0, result);
+ }
+
+ [Fact]
+ public void Run_ShouldReturn1_WhenForceIsFalseAndDirectoryExists()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Force = false };
+ fileSystem.FileExists(Arg.Any()).Returns(true);
+
+ var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site);
+
+ // Act
+ var result = newSiteCommand.Run();
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void Run_ShouldReturn0_WhenForceIsTrueAndDirectoryExists()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Force = true };
+ fileSystem.FileExists(Arg.Any()).Returns(true);
+
+ var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site);
+
+ // Act
+ var result = newSiteCommand.Run();
+
+ // Assert
+ Assert.Equal(0, result);
+ }
+
+ [Fact]
+ public void Run_ShouldReturn1_WhenExportThrowsException()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Force = true };
+ fileSystem.FileExists(Arg.Any()).Returns(false);
+ site.Parser
+ .When(x => x.Export(Arg.Any(), Arg.Any()))
+ .Do(x => { throw new ArgumentNullException(); });
+
+ var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site);
+
+ // Act
+ var result = newSiteCommand.Run();
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void Run_ShouldCallCreateDirectory_ForEachFolder()
+ {
+ // Arrange
+ var options = new NewSiteOptions { Output = "test", Force = false };
+ site.SourceFolders.Returns(["folder1", "folder2"]);
+ fileSystem.FileExists(Arg.Any()).Returns(false);
+
+ var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site);
+
+ // Act
+ newSiteCommand.Run();
+
+ // Assert
+ fileSystem.Received(2).CreateDirectory(Arg.Any());
+ }
+}