diff --git a/source/Models/CommandLineOptions/NewSiteOptions.cs b/source/Models/CommandLineOptions/NewSiteOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c9fb348eb0343cf235f3327fd7b1e21b9cb2792e
--- /dev/null
+++ b/source/Models/CommandLineOptions/NewSiteOptions.cs
@@ -0,0 +1,40 @@
+using CommandLine;
+
+namespace SuCoS.Models.CommandLineOptions;
+
+///
+/// Command line options to generate a simple site from scratch.
+///
+[Verb("newsite", false, HelpText = "Generate a simple site from scratch")]
+public class NewSiteOptions
+{
+ ///
+ /// The path of the output files.
+ ///
+ [Option('o', "output", Required = false, HelpText = "Output directory path")]
+ public required string Output { get; init; }
+
+ ///
+ /// Force site creation.
+ ///
+ [Option('f', "force", Required = false, HelpText = "Force site creation")]
+ public bool Force { get; init; }
+
+ ///
+ /// Site title.
+ ///
+ [Option("title", Required = false, HelpText = "Site title")]
+ public required string Title { get; init; } = "My Site";
+
+ ///
+ /// Site description.
+ ///
+ [Option("description", Required = false, HelpText = "Site description")]
+ public required 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/";
+}
diff --git a/source/Models/Site.cs b/source/Models/Site.cs
index 4b56ce34943440194a34f0a4431d05289e72e550..f6f26d77e9548f1f6847ea63f358570da327fdc3 100644
--- a/source/Models/Site.cs
+++ b/source/Models/Site.cs
@@ -78,6 +78,15 @@ public class Site : ISite
///
public string SourceThemeStaticPath => Path.Combine(SourceThemePath, "static");
+ ///
+ /// List of all basic source folders
+ ///
+ public IEnumerable SourceFodlers => [
+ SourceContentPath,
+ SourceStaticPath,
+ SourceThemePath
+ ];
+
///
/// List of all pages, including generated.
///
diff --git a/source/NewSiteCommand.cs b/source/NewSiteCommand.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7d01736d791737e00a626e42e48407d498686e0b
--- /dev/null
+++ b/source/NewSiteCommand.cs
@@ -0,0 +1,89 @@
+using Serilog;
+using SuCoS.Models;
+using SuCoS.Models.CommandLineOptions;
+using YamlDotNet.Serialization;
+
+namespace SuCoS;
+
+///
+/// Check links of a given site.
+///
+public sealed partial class NewSiteCommand(NewSiteOptions settings, ILogger logger)
+{
+ ///
+ /// Run the app
+ ///
+ ///
+ public int Run()
+ {
+ var siteSettings = new SiteSettings()
+ {
+ Title = settings.Title,
+ Description = settings.Description,
+ BaseURL = settings.BaseURL,
+ };
+
+ // TODO: Refactor Site class to not need YAML parser nor FrontMatterParser
+ var site = new Site(new ServeOptions() { SourceOption = settings.Output }, siteSettings, null!, logger, null);
+
+ var outputPath = Path.GetFullPath(settings.Output);
+ var siteSettingsPath = Path.Combine(outputPath, "sucos.yaml");
+
+ if (File.Exists(siteSettingsPath) && !settings.Force)
+ {
+ logger.Error("{directoryPath} already exists", outputPath);
+ return 1;
+ }
+
+ logger.Information("Creating a new site: {title} at {outputPath}", siteSettings.Title, outputPath);
+
+ CreateFolders(site.SourceFodlers);
+
+ try
+ {
+ ExportSiteSettings(siteSettings, siteSettingsPath);
+ }
+ catch (Exception ex)
+ {
+ logger.Error("Failed to export site settings: {ex}", ex);
+ return 1;
+ }
+ logger.Information("Done");
+
+ return 0;
+ }
+
+ ///
+ /// Create the standard folders
+ ///
+ ///
+ private void CreateFolders(IEnumerable folders)
+ {
+ foreach (var folder in folders)
+ {
+ logger.Information("Creating {folder}", folder);
+ Directory.CreateDirectory(folder);
+ }
+ }
+
+ // TODO: move all YAML parsing to this own class
+ #region YAML
+ ///
+ /// YamlDotNet parser to loosely parse the YAML file. Used to include all non-matching fields
+ /// into Params.
+ ///
+ ISerializer yamlDeserializer = new SerializerBuilder()
+ .IgnoreFields()
+ .ConfigureDefaultValuesHandling(
+ DefaultValuesHandling.OmitEmptyCollections
+ | DefaultValuesHandling.OmitDefaults
+ | DefaultValuesHandling.OmitNull)
+ .Build();
+
+ void ExportSiteSettings(SiteSettings siteSettings, string siteSettingsPath)
+ {
+ var siteSettingsConverted = yamlDeserializer.Serialize(siteSettings);
+ File.WriteAllText(siteSettingsPath, siteSettingsConverted);
+ }
+ #endregion YAML
+}
\ No newline at end of file
diff --git a/source/Program.cs b/source/Program.cs
index ab464322857b54b289e972ee39577f9a2d00ba1f..02a810e8cb082df4996170619c7845c01498cc2b 100644
--- a/source/Program.cs
+++ b/source/Program.cs
@@ -43,7 +43,7 @@ public class Program(ILogger logger)
{
OutputLogo();
OutputWelcome();
- return await CommandLine.Parser.Default.ParseArguments(args)
+ return await CommandLine.Parser.Default.ParseArguments(args)
.WithParsed(options =>
{
logger = CreateLogger(options.Verbose);
@@ -90,8 +90,14 @@ public class Program(ILogger logger)
},
(CheckLinkOptions options) =>
{
+ logger = CreateLogger(options.Verbose);
var command = new CheckLinkCommand(options, logger);
return command.Run();
+ },
+ (NewSiteOptions options) =>
+ {
+ var command = new NewSiteCommand(options, logger);
+ return Task.FromResult(command.Run());
},
errs => Task.FromResult(1)
);