diff --git a/Program.cs b/Program.cs index edfe1f8..447b581 100644 --- a/Program.cs +++ b/Program.cs @@ -1,223 +1,317 @@ -/* - * csSiteGen - A static site generator written in c# - * Copyright © 2022 Robert Morrison - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using Serilog.Events; -using Serilog; -using System.CommandLine.Builder; -using System.CommandLine.Help; -using System.CommandLine.Parsing; -using System.CommandLine; -using System.Diagnostics; -using System.Reflection; -using Spectre.Console; - -namespace csSiteGen; - -class Program -{ - - static int Main(string[] args) - { - - // Get the current versiion number - string? version = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.InformationalVersion; - - // Configure logger - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .WriteTo.File("log.log") - .CreateLogger(); - - Log.Information("Starting New Instance of csSiteGen"); - - if (version is not null) - { - Log.Information("Version: {ver}", version); - } - else - { - Log.Warning("Cannot get Version Information"); - } - - // It is very likely that this program will only work on linux. As such it is worth warning the user about this. - if (!OperatingSystem.IsLinux()) - { - Log.Warning("This program has only been tested on linux and cannot be assumed to work on other Operating Systems"); - // AnsiConsole.MarkupLine("[[[yellow]Warning[/]]] This program is only tested on linux systems, it may not work on this Operating System."); - } - - Stopwatch TotalExecutionTime = Stopwatch.StartNew(); - - var inputDirectoryOption = new Option( - name: "--input", - description: "The directory that contains the site source."); - inputDirectoryOption.IsRequired = true; - inputDirectoryOption.AddValidator(result => - { - if (!result.GetValueForOption(inputDirectoryOption)!.Exists) - { - result.ErrorMessage = $"Input directory {result.GetValueForOption(inputDirectoryOption)!.FullName} does not exist"; - } - }); - - var outputDirectoryOption = new Option( - name: "--output", - description: "The directory that the site should be output to."); - outputDirectoryOption.IsRequired = true; - - var rootCommand = new RootCommand("csSiteGen"); - - var cleanCommand = new Command("clean", "Clean the output directory"); - cleanCommand.AddOption(outputDirectoryOption); - cleanCommand.SetHandler(async (directory) => - { - await Task.Run(() => - { - Clean(directory); - }); - } ,outputDirectoryOption); - - var convertCommand = new Command("convert", "Convert the input directory and place the files in the output directory."); - convertCommand.AddOption(inputDirectoryOption); - convertCommand.AddOption(outputDirectoryOption); - convertCommand.SetHandler(async (inputDir, outputDir) => - { - await Task.Run(() => - { - Convert(inputDir, outputDir); - }); - }, inputDirectoryOption, outputDirectoryOption); - - rootCommand.AddCommand(cleanCommand); - rootCommand.AddCommand(convertCommand); - - var parser = new CommandLineBuilder(rootCommand) - .UseDefaults() - .Build(); - - parser.Invoke(args); - - - TotalExecutionTime.Stop(); - Log.Information("TotalExecutionTime {time:000}ms", TotalExecutionTime.ElapsedMilliseconds); - - Log.CloseAndFlush(); - return 0; - } - - static int Convert(DirectoryInfo inputDir, DirectoryInfo outputDir) - { - AnsiConsole.Console.Profile.Capabilities.Ansi = true; - - List siteFiles = new(); - - Utils.GetFiles(inputDir).ForEach(x => siteFiles.Add(new SiteFile(x))); - Log.Information("SiteFiles: {@sf} {count}", siteFiles, siteFiles.Count); - - Console.WriteLine($"Converting {siteFiles.Count} files from {inputDir.FullName} to {outputDir.FullName}"); - RuntimeSettings settings = new(inputDir,outputDir); - - - Dictionary fileStatus = new(); - Log.Debug("fileStatus {@fileStatus}",fileStatus); - - AnsiConsole.Progress() - .AutoRefresh(true) - .Columns(new ProgressColumn[] - { - new TaskDescriptionColumn(), - new ProgressBarColumn(), - new PercentageColumn(), - new RemainingTimeColumn(), - new SpinnerColumn() - }) - .Start(ctx => - { - var tasks = siteFiles.Select(x => ctx.AddTask($"Converting {x.Name}")).ToList(); - var overallTask = ctx.AddTask("[bold]Converting Files[/]"); - overallTask.MaxValue = siteFiles.Count(); - - for (int i = 0; i < siteFiles.Count; i++) - { - tasks[i].MaxValue = 1; - Log.Information("Converting file {name} {i}/{count}",siteFiles[i].Name,i+1,siteFiles.Count()); - bool res = siteFiles[i].Convert(settings); - tasks[i].Increment(1); - tasks[i].StopTask(); - if (!res) - { - Log.Warning("{name} Failed...",siteFiles[i].FullName); - tasks[i].Description += " [red]FAILED[/]"; - } - Log.Information("adding {@siteFile} conversion status to fileStatus", siteFiles[i]); - Log.Debug("FileStatus {@fileStatus}",fileStatus); - fileStatus.Add(siteFiles[i].FullName,res); - overallTask.Increment(1); - } - }); - - Log.Information("Conversion Status {@status}", fileStatus); - - var Failed = fileStatus.Where(x => x.Value == false); - if ( Failed.Count() > 0) - { - foreach (var fail in Failed) - { - AnsiConsole.MarkupLineInterpolated( - $"[red]File [blue]\"{fail.Key}\"[/] failed to convert[/]"); - AnsiConsole.MarkupLine("[yellow]See log for more details[/]"); - } - } - - return 0; - } - - static int Clean(DirectoryInfo outputDir) - { - if (!outputDir.Exists) - { - Log.Warning("Not deleting {dir} as it doesn't exist",outputDir.FullName); - AnsiConsole.MarkupLineInterpolated($"[bold][[[yellow]Warning[/]]][/] Not cleaning [blue]\"{outputDir}\"[/] as it does not exist."); - return 0; // success because it doesn't exist. - } - try - { - Log.Information("Cleaning {dir}",outputDir.FullName); - AnsiConsole.MarkupInterpolated($"Cleaning [blue]\"{outputDir.FullName}\"[/]"); - outputDir.Delete(recursive: true); - outputDir.Create(); - AnsiConsole.MarkupLine(" [bold][[[green]OK[/]]][/]"); - AnsiConsole.MarkupLineInterpolated($"\t[grey]>>[/] [green]All files in {outputDir.FullName} purged successfully[/]"); - return 0; - } - catch (System.Security.SecurityException e) - { - AnsiConsole.MarkupLine(" [bold][[[red]Fail[/]]][/]"); - AnsiConsole.MarkupLine("[orangered1]See log for more details about what went wrong.[/]"); - Log.Error(e, "Failed to delete directory {dir} due to permission error.", outputDir.FullName); - return 1; - } - catch (Exception e) - { - AnsiConsole.MarkupLine(" [red][[[bold]Fail[/]]][/]"); - AnsiConsole.MarkupLine("[orangered1]See log for more details about what went wrong.[/]"); - Log.Error(e, "Failed to delete/create directory {dir}", outputDir.FullName); - return 1; - } - } -} +/* + * csSiteGen - A static site generator written in c# + * Copyright © 2022 Robert Morrison + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Serilog.Events; +using Serilog; +using System.CommandLine.Builder; +using System.CommandLine.Help; +using System.CommandLine.Parsing; +using System.CommandLine; +using System.Diagnostics; +using System.Reflection; +using Spectre.Console; +using System.Text.Json; + +namespace csSiteGen; + +class Program +{ + + static int Main(string[] args) + { + + // Get the current version number + string? version = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.InformationalVersion; + + // Only log to file + // Logging to the console is BAD practice as it tends to be messy. + // TODO: Log to a known location using environment to find the correct location. + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.File("log.log") + .CreateLogger(); + + Log.Information("Starting New Instance of csSiteGen"); + + if (version is not null) + { + Log.Information("Version: {ver}", version); + } + else + { + Log.Warning("Cannot get Version Information"); + } + + // It is very likely that this program will only work on linux. As such it is worth warning the user about this. + // With further testing and handling of any edge cases it _may_ be possible to have this work anywhere. + if (!OperatingSystem.IsLinux()) + { + Log.Warning("This program has only been tested on linux and cannot be assumed to work on other Operating Systems"); + // AnsiConsole.MarkupLine("[[[yellow]Warning[/]]] This program is only tested on linux systems, it may not work on this Operating System."); + } + + Stopwatch TotalExecutionTime = Stopwatch.StartNew(); + + /* !! IMPORTANT !! + WARN: + This code uses system.commandline which is still in pre-release + the following section of code will contain comments to explain the intent of the programmer + which may be useful if system.commandline has breaking changes + */ + + + // First the option for the project directory is created + var ProjectDirectoryOption = new Option( + name: "--project", + description: "The Directory for the project"); + ProjectDirectoryOption.IsRequired = false; // it is not required as not providing it infers that the current directory is the project directory + // If the option is used then the input is validated before control passes to any of the actual code. + ProjectDirectoryOption.AddValidator(result => + { + if (!result.GetValueForOption(ProjectDirectoryOption)!.Exists) + { + result.ErrorMessage = $"Project directory {result.GetValueForOption(ProjectDirectoryOption)} does not exist."; + } + } + ); + + // The root command is the entry point for commandline but otherwise does nothing. + var rootCommand = new RootCommand("csSiteGen"); + + + // TODO: Verify if the use of async in these functions is necessary + + // This creates the command for cleaning a projects output directory. + var cleanCommand = new Command("clean", "Clean the projects output directory"); + cleanCommand.AddOption(ProjectDirectoryOption); // This command can use the project directory option we created earlier + cleanCommand.SetHandler(async (ProjectDirectory) => + { + await Task.Run(() => + { + Clean(ProjectDirectory); + }); + },ProjectDirectoryOption); + + // This creates the command for actually converting the project. + var convertCommand = new Command("convert", "Convert the projects input directory and place the files in the output directory."); + convertCommand.AddOption(ProjectDirectoryOption); // This command can use the project directory option + convertCommand.SetHandler(async (ProjectDirectory) => + { + await Task.Run(() => + { + Convert(ProjectDirectory); + }); + },ProjectDirectoryOption); + + // Adding the commands to the root command makes them actually callable on the commandline + rootCommand.AddCommand(cleanCommand); + rootCommand.AddCommand(convertCommand); + + // The parser is what actually handles the arguments and dispatches them to the appropriate commands. + // This is used instead of the simpler method of just Invoking the root command as it automatically creates usage statements. + // It also makes a user aware that a subcommand needs to be used. + var parser = new CommandLineBuilder(rootCommand) + .UseDefaults() + .Build(); + + parser.Invoke(args); + + + TotalExecutionTime.Stop(); + Log.Information("TotalExecutionTime {time:000}ms", TotalExecutionTime.ElapsedMilliseconds); + + // Providing there were no early exits it is best to properly close the log before we exit. + Log.CloseAndFlush(); + return 0; + } + + static int Convert(DirectoryInfo? ProjectDirectory) + { + Log.Information("Convert command was called, beginning conversion."); + + // WARN: This is only temporary as Spectre.Console does not recognise some Linux terminals + // A better solution that checks the terminal value and sets this option should be added in the future. + AnsiConsole.Console.Profile.Capabilities.Ansi = true; + + + // NOTE: Future refactors may merge ProjectSettings and RuntimeSettings + ProjectSettings projectSettings = GetProjectSettings(ProjectDirectory); + + DirectoryInfo inputDir = new(projectSettings.Source); + DirectoryInfo outputDir = new(projectSettings.Destination); + + RuntimeSettings settings = new(inputDir,outputDir); + settings.setBaseUrl(projectSettings.BaseUrl); + + List siteFiles = new(); + + Utils.GetFiles(inputDir).ForEach(x => siteFiles.Add(new SiteFile(x))); + Log.Information("SiteFiles: {@sf} {count}", siteFiles, siteFiles.Count); + + Console.WriteLine($"Converting {siteFiles.Count} files from {inputDir.FullName} to {outputDir.FullName}"); + + + Dictionary fileStatus = new(); + Log.Debug("fileStatus {@fileStatus}",fileStatus); + + AnsiConsole.Progress() + .AutoRefresh(true) + .Columns(new ProgressColumn[] + { + new TaskDescriptionColumn(), + new ProgressBarColumn(), + new PercentageColumn(), + new RemainingTimeColumn(), + new SpinnerColumn() + }) + .Start(ctx => + { + var tasks = siteFiles.Select(x => ctx.AddTask($"Converting {x.Name}")).ToList(); + var overallTask = ctx.AddTask("[bold]Converting Files[/]"); + overallTask.MaxValue = siteFiles.Count(); + + for (int i = 0; i < siteFiles.Count; i++) + { + tasks[i].MaxValue = 1; + Log.Information("Converting file {name} {i}/{count}",siteFiles[i].Name,i+1,siteFiles.Count()); + bool res = siteFiles[i].Convert(settings); + tasks[i].Increment(1); + tasks[i].StopTask(); + if (!res) + { + Log.Warning("{name} Failed...",siteFiles[i].FullName); + tasks[i].Description += " [red]FAILED[/]"; + } + Log.Information("adding {@siteFile} conversion status to fileStatus", siteFiles[i]); + Log.Debug("FileStatus {@fileStatus}",fileStatus); + fileStatus.Add(siteFiles[i].FullName,res); + overallTask.Increment(1); + } + }); + + Log.Information("Conversion Status {@status}", fileStatus); + + var Failed = fileStatus.Where(x => x.Value == false); + if ( Failed.Count() > 0) + { + foreach (var fail in Failed) + { + AnsiConsole.MarkupLineInterpolated( + $"[red]File [blue]\"{fail.Key}\"[/] failed to convert[/]"); + AnsiConsole.MarkupLine("[yellow]See log for more details[/]"); + } + } + + return 0; + } + + static int Clean(DirectoryInfo? ProjectDirectory) + { + Log.Information("Clean command was called, Beginning cleaning"); + + // NOTE: Future refactors may merge ProjectSettings and RuntimeSettings + ProjectSettings projectSettings = GetProjectSettings(ProjectDirectory); + + DirectoryInfo inputDir = new(projectSettings.Source); + DirectoryInfo outputDir = new(projectSettings.Destination); + + if (!outputDir.Exists) + { + Log.Warning("Not deleting {dir} as it doesn't exist",outputDir.FullName); + AnsiConsole.MarkupLineInterpolated($"[bold][[[yellow]Warning[/]]][/] Not cleaning [blue]\"{outputDir}\"[/] as it does not exist."); + return 0; // success because it doesn't exist. + } + try + { + Log.Information("Cleaning {dir}",outputDir.FullName); + AnsiConsole.MarkupInterpolated($"Cleaning [blue]\"{outputDir.FullName}\"[/]"); + outputDir.Delete(recursive: true); + outputDir.Create(); + AnsiConsole.MarkupLine(" [bold][[[green]OK[/]]][/]"); + AnsiConsole.MarkupLineInterpolated($"\t[grey]>>[/] [green]All files in {outputDir.FullName} purged successfully[/]"); + return 0; + } + catch (System.Security.SecurityException e) + { + AnsiConsole.MarkupLine(" [bold][[[red]Fail[/]]][/]"); + AnsiConsole.MarkupLine("[orangered1]See log for more details about what went wrong.[/]"); + Log.Error(e, "Failed to delete directory {dir} due to permission error.", outputDir.FullName); + return 1; + } + catch (Exception e) + { + AnsiConsole.MarkupLine(" [red][[[bold]Fail[/]]][/]"); + AnsiConsole.MarkupLine("[orangered1]See log for more details about what went wrong.[/]"); + Log.Error(e, "Failed to delete/create directory {dir}", outputDir.FullName); + return 1; + } + } + + static void EnforceConsistency(ProjectSettings projectSettings) + { + // Grab the metadata. + + // Read the metadata. + + // Find deleted files. + + // Figure out what the new name for those files would be. + + // Remove those files. + + } + + static ProjectSettings GetProjectSettings(DirectoryInfo? ProjectDirectory) + { + // TODO: implement proper error handling where file access is performed. + + if (ProjectDirectory is null) + { + // use the current directory if no project directory is passed. + ProjectDirectory = new DirectoryInfo("."); + } + Log.Information("{projectdir} => fullname {pdfn}",ProjectDirectory, ProjectDirectory.FullName); + FileInfo projectFile = new (Path.Combine(ProjectDirectory.FullName,"cssitegen.json")); + + + if (!projectFile.Exists) + { + Log.Fatal("Cannot locate project file {pf} in {dir}",projectFile,ProjectDirectory); + Environment.Exit(1); + } + + Log.Information("Located Project File {pf}",projectFile.FullName); + ProjectSettings? projectSettings = JsonSerializer + .Deserialize( + projectFile + .OpenText() + .ReadToEnd() + ); + + if (projectSettings is null) + { + Log.Fatal("Cannot deserialize projectFile"); + Environment.Exit(1); + } + projectSettings.setProjectRoot(ProjectDirectory); + + Log.Information("{@ps}",projectSettings); + + return projectSettings; + } +} diff --git a/ProjectSettings/ProjectSettings.cs b/ProjectSettings/ProjectSettings.cs new file mode 100644 index 0000000..5c2e412 --- /dev/null +++ b/ProjectSettings/ProjectSettings.cs @@ -0,0 +1,44 @@ +using System.Text.Json.Serialization; +// project settings is a user accessible config to set the Source and destination of a site +// This may include more scope in the future such as holding the site base address etc.. + +class ProjectSettings +{ + private string _Source; + private string _Destination; + private DirectoryInfo? _ProjectRoot; + public string? BaseUrl {get; private set;} + + public string Source {get { + if (_ProjectRoot is null) + { + return _Source; + } + return Path.Combine(_ProjectRoot.FullName,_Source); + }} + public string Destination {get { + if (_ProjectRoot is null) + { + return _Destination; + } + return Path.Combine(_ProjectRoot.FullName,_Destination); + }} + + [JsonConstructor] + public ProjectSettings(String source, String destination, string baseUrl) { + _Source = source; + _Destination = destination; + BaseUrl = baseUrl; + } + + public void setProjectRoot(string projectRoot) { + _ProjectRoot = new(projectRoot); + } + public void setProjectRoot(DirectoryInfo projectRoot) { + _ProjectRoot = projectRoot; + } + + public void setBaseUrl(string baseUrl) { + BaseUrl = baseUrl; + } +} diff --git a/RuntimeSettings/RuntimeSettings.cs b/RuntimeSettings/RuntimeSettings.cs index 92aff3e..8c7e627 100644 --- a/RuntimeSettings/RuntimeSettings.cs +++ b/RuntimeSettings/RuntimeSettings.cs @@ -2,11 +2,12 @@ namespace csSiteGen; /// -/// Class RuntimeSettings

Contains all the settings that could be loaded from the commandline. +/// Class RuntimeSettings Contains all the settings that could be loaded from the commandline. ///
public class RuntimeSettings { public DirectoryInfo InputDirectory {get; private set;} public DirectoryInfo OutputDirectory {get; private set;} + public string? BaseUrl {get; private set;} public RuntimeSettings(string inputDirectory, string outputDirectory){ @@ -26,4 +27,7 @@ public class RuntimeSettings { */ } + public void setBaseUrl(string? baseurl) { + BaseUrl = baseurl; + } } diff --git a/SiteFile/SiteFile.ConverterFunctions.cs b/SiteFile/SiteFile.ConverterFunctions.cs index d15a414..0710b93 100644 --- a/SiteFile/SiteFile.ConverterFunctions.cs +++ b/SiteFile/SiteFile.ConverterFunctions.cs @@ -15,6 +15,11 @@ public static class Conversions{ {".md", Pandoc}, }; + private static readonly string[] BaseUrlFiletypes = { + ".md", + ".html" + }; + /// /// TEST FUNCTION. @@ -28,7 +33,7 @@ public static class Conversions{ } /// - /// Copy the file verbatim + /// Copy the file verbatim (doing any baseurl replacements if needed) /// public static bool RawCpy(FileInfo file, RuntimeSettings settings){ FileInfo newPath = new FileInfo(GetNewName(file,settings,null)); @@ -41,7 +46,14 @@ public static class Conversions{ } try { - file.CopyTo(newPath.FullName, overwrite: true); + if (BaseUrlFiletypes.Contains(file.Extension)) + { + File.WriteAllText(newPath.FullName, BaseUrlReplace(file, settings)); + } + else + { + file.CopyTo(newPath.FullName, overwrite: true); + } } catch (Exception e){ Log.Fatal(e,"Copy Failed"); @@ -54,6 +66,11 @@ public static class Conversions{ /// Execute pandoc on the file, automatically detecting the template to use. /// public static bool Pandoc(FileInfo file, RuntimeSettings settings){ + // NOTE: Some of the code later where the tmpfile is created for baseurl replacement may be too safe. + // the extension checks may be unnecessary, but this depends on if this function will be retooled to run pandoc for different conversions. + // for now I have take the safer approach, but the leaner approach may be used in the future when the project is more mature + + Log.Information("Attempting to convert {file} using pandoc",file.Name); // Look for pandoc @@ -91,7 +108,37 @@ public static class Conversions{ searchDir = searchDir.Parent; } while (searchDir != settings.InputDirectory); // Check last as we want to search the InputDirectory - string pandocArgs = $"{file.FullName} -o {GetNewName(file,settings,".html")}"; + // the empty string is used as it has a defined identity + string tmpFile = string.Empty; + if (BaseUrlFiletypes.Contains(file.Extension)) + { + Log.Information("Replacing baseurl for file {f}",file.FullName); + tmpFile = Path.Join(Path.GetTempPath(),"pandoc",file.Name); + Directory.CreateDirectory(Path.GetDirectoryName(tmpFile)!); // NOTE: It is practially impossible that this would actually return null + File.Create(tmpFile).Close(); // TODO: Use the filestream provided by File.Create within a using block to write the text + File.WriteAllText(tmpFile,BaseUrlReplace(file,settings)); + + if (template is not null) + { + Log.Information("Replacing baseurl in template file"); + string tmpTemplateFile = Path.Join(Path.GetTempPath(),"pandoc",template.Name); + Directory.CreateDirectory(Path.GetDirectoryName(tmpTemplateFile)!); // NOTE: It is practially impossible that this would actually return null + File.Create(tmpTemplateFile).Close(); // TODO: Use the filestream provided by File.Create within a using block to write the text + File.WriteAllText(tmpTemplateFile,BaseUrlReplace(template,settings)); + template = new(tmpTemplateFile); + } + } + + string pandocArgs; + // If we have created a temporary file we need to ensure that we use it. + if (!string.IsNullOrEmpty(tmpFile)) + { + pandocArgs = $"{tmpFile} -o {GetNewName(file,settings,".html")}"; + } + else + { + pandocArgs = $"{file.FullName} -o {GetNewName(file,settings,".html")}"; + } if (template is not null) { @@ -103,7 +150,18 @@ public static class Conversions{ Log.Warning("Pandoc template for {file} not found",file.Name); } - return RunExternalProgram(pandoc,pandocArgs); + if (!Directory.Exists(Path.GetDirectoryName(GetNewName(file,settings,".html")))) + { + Directory.CreateDirectory(Path.GetDirectoryName(GetNewName(file,settings,".html"))!); + } + + bool pandocReturn = RunExternalProgram(pandoc,pandocArgs); + // If we made a tmpfile delete it after running pandoc against it. + if (!string.IsNullOrEmpty(tmpFile)) + { + File.Delete(tmpFile); + } + return pandocReturn; } @@ -138,8 +196,8 @@ public static class Conversions{ RunProgram.WaitForExit(); - Log.Debug("{program} stdout {stdout}", program, stdout); - Log.Debug("{program} stderr {stderr}", program, stderr); + Log.Debug("{program} STDOUT:\n{stdout}", program, stdout); + Log.Debug("{program} STDERR:\n{stderr}", program, stderr); if (RunProgram.ExitCode != 0) { @@ -155,4 +213,23 @@ public static class Conversions{ .Replace(settings.InputDirectory.FullName, settings.OutputDirectory.FullName) .Replace(file.Extension,newExtension ?? file.Extension); } + + private static string? BaseUrlReplace(FileInfo file, RuntimeSettings settings){ + Log.Information("Doing BaseUrlReplace for {f}", file.FullName); + // Read the file + using (StreamReader FileReader = file.OpenText()) + { + string filestring = FileReader.ReadToEnd(); + + if (settings.BaseUrl is null) + { + Log.Warning("BaseUrl is null, replacing templateString with nothing."); + return filestring.Replace("%BASEURL%",""); + } + + Log.Information("Replacing templateString with {BaseUrl}",settings.BaseUrl); + return filestring.Replace("%BASEURL%",settings.BaseUrl); + } + } + } diff --git a/SiteFile/SiteFile.cs b/SiteFile/SiteFile.cs index 0142c57..933d945 100644 --- a/SiteFile/SiteFile.cs +++ b/SiteFile/SiteFile.cs @@ -74,7 +74,11 @@ public partial class SiteFile // NOTE: By this point Metadata CANNOT be null as LoadMetadata would either have loaded the JSON or instantiated blank MetaData // The compiler however is unaware of this as it uses side effects, so we tell it there is no possible null value here. - return (Metadata!.GetValueOrDefault(info.FullName, DateTime.MinValue) == info.LastWriteTimeUtc); + Log.Debug("Attempting to check metadata for file {f}",info.FullName); + Log.Debug("METADATA={MD}",Metadata!.GetValueOrDefault(info.FullName,DateTime.MinValue)); + Log.Debug("FILETIME={FT}",info.LastWriteTimeUtc); + Log.Debug("RES={res}",(Metadata!.GetValueOrDefault(info.FullName, DateTime.MinValue) != info.LastWriteTimeUtc)); + return (Metadata!.GetValueOrDefault(info.FullName, DateTime.MinValue) != info.LastWriteTimeUtc); } diff --git a/TODO b/TODO index 01c937f..ad9dcb2 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,9 @@ +- UPDATE README + +- Make the code such that the metadata file knows what source file an output file came from, this will allow us to delete files from source + and force consistency by removing them from the dst directory too. + - Add a pre-commit or other type of hook to ensure that the Testing directory is properly cleaned and reset before commit. -- Double check that links are processed correctly by testing against a copy of my blog source. - - Also add the appropriate metadata to the markdown files to make sure that they are properly detected. - - Potentially host a local Test version to fully ensure that everything can be made to work properly. + - Ensure that the .gitkeep file is placed into Testing/dst + - Look into the possiblity of using a TemplateTemplate, And generating the pandoc template on the fly making it possible to switch out domian/prefix stuff diff --git a/Testing/cssitegen.json b/Testing/cssitegen.json new file mode 100644 index 0000000..fd0fa32 --- /dev/null +++ b/Testing/cssitegen.json @@ -0,0 +1,5 @@ +{ + "Source" : "./src", + "Destination" : "./dst", + "BaseUrl" : null +} diff --git a/Utils/Utils.GetFiles.cs b/Utils/Utils.GetFiles.cs index a1ee635..4838cec 100644 --- a/Utils/Utils.GetFiles.cs +++ b/Utils/Utils.GetFiles.cs @@ -4,6 +4,6 @@ public static partial class Utils { // Abstract Directory.GetFiles to get a List as it will be easier to handle later. public static List GetFiles(DirectoryInfo dir){ - return dir.GetFiles("*",SearchOption.AllDirectories).ToList(); + return dir.GetFiles("*",SearchOption.AllDirectories).Where(x => x.Name != ".template").ToList(); } } diff --git a/Utils/Utils.PathSearch.cs b/Utils/Utils.PathSearch.cs index fdb687b..d28c910 100644 --- a/Utils/Utils.PathSearch.cs +++ b/Utils/Utils.PathSearch.cs @@ -3,6 +3,8 @@ namespace csSiteGen; public static partial class Utils { + // As the PathSearch utility will be called for every convertible file + // It has been memoized which mean subsequent calls for the same argument just return the result static Dictionary PathSearchMemo = new(); /// diff --git a/csSiteGen.csproj b/csSiteGen.csproj index a142bd7..9751fb6 100644 --- a/csSiteGen.csproj +++ b/csSiteGen.csproj @@ -4,7 +4,7 @@ Exe $(RELEASE_VERSION) - 0.0.1 + 0.0.2 $([System.DateTime]::UtcNow.ToString(`yyyyMMdd-HHmm`))