From e6cf474c490679fdc5c312db356928030dc7fab1 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 3 Apr 2024 15:36:50 -0500 Subject: [PATCH 1/2] feat: newsite command creates a site in a given folder --- .../CommandLineOptions/NewSiteOptions.cs | 40 +++++++++ source/NewSiteCommand.cs | 85 +++++++++++++++++++ source/Program.cs | 8 +- 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 source/Models/CommandLineOptions/NewSiteOptions.cs create mode 100644 source/NewSiteCommand.cs diff --git a/source/Models/CommandLineOptions/NewSiteOptions.cs b/source/Models/CommandLineOptions/NewSiteOptions.cs new file mode 100644 index 0000000..c9fb348 --- /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/NewSiteCommand.cs b/source/NewSiteCommand.cs new file mode 100644 index 0000000..8177fd2 --- /dev/null +++ b/source/NewSiteCommand.cs @@ -0,0 +1,85 @@ +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) +{ + readonly string[] folders = [ + "content", + "static", + "theme" + ]; + + /// + /// Run the app + /// + /// + public int Run() + { + var siteSettings = new SiteSettings() + { + Title = settings.Title, + Description = settings.Description, + BaseURL = settings.BaseURL, + }; + 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; + } + + CreateFolders(outputPath); + + logger.Information("Creating a new site: {title} at {outputPath}", siteSettings.Title, outputPath); + + try + { + ExportSiteSettings(siteSettings, siteSettingsPath); + } + catch (Exception ex) + { + logger.Error("Failed to export site settings: {ex}", ex); + return 1; + } + logger.Information("Done"); + + return 0; + } + + private void CreateFolders(string outputPath) + { + // Create the standard folders + Directory.CreateDirectory(outputPath); + foreach (var folder in folders) + { + Directory.CreateDirectory(Path.Combine(outputPath, folder)); + } + } + + /// + /// 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); + } +} \ No newline at end of file diff --git a/source/Program.cs b/source/Program.cs index ab46432..02a810e 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) ); -- GitLab From 156c5919655bdb3fa7894b8fc6bb7bc8610d8c2b Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 3 Apr 2024 16:02:50 -0500 Subject: [PATCH 2/2] chore: using the Site class to provide folders to create --- source/Models/Site.cs | 9 +++++++++ source/NewSiteCommand.cs | 28 ++++++++++++++++------------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/source/Models/Site.cs b/source/Models/Site.cs index 4b56ce3..f6f26d7 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 index 8177fd2..7d01736 100644 --- a/source/NewSiteCommand.cs +++ b/source/NewSiteCommand.cs @@ -10,12 +10,6 @@ namespace SuCoS; /// public sealed partial class NewSiteCommand(NewSiteOptions settings, ILogger logger) { - readonly string[] folders = [ - "content", - "static", - "theme" - ]; - /// /// Run the app /// @@ -28,6 +22,10 @@ public sealed partial class NewSiteCommand(NewSiteOptions settings, ILogger logg 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"); @@ -37,10 +35,10 @@ public sealed partial class NewSiteCommand(NewSiteOptions settings, ILogger logg return 1; } - CreateFolders(outputPath); - logger.Information("Creating a new site: {title} at {outputPath}", siteSettings.Title, outputPath); + CreateFolders(site.SourceFodlers); + try { ExportSiteSettings(siteSettings, siteSettingsPath); @@ -55,16 +53,21 @@ public sealed partial class NewSiteCommand(NewSiteOptions settings, ILogger logg return 0; } - private void CreateFolders(string outputPath) + /// + /// Create the standard folders + /// + /// + private void CreateFolders(IEnumerable folders) { - // Create the standard folders - Directory.CreateDirectory(outputPath); foreach (var folder in folders) { - Directory.CreateDirectory(Path.Combine(outputPath, folder)); + 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. @@ -82,4 +85,5 @@ public sealed partial class NewSiteCommand(NewSiteOptions settings, ILogger logg var siteSettingsConverted = yamlDeserializer.Serialize(siteSettings); File.WriteAllText(siteSettingsPath, siteSettingsConverted); } + #endregion YAML } \ No newline at end of file -- GitLab