From da24073f1499ab0b3e6c223f325dcb1ce7e41588 Mon Sep 17 00:00:00 2001 From: Robert Morrison Date: Wed, 12 Mar 2025 02:31:54 +0000 Subject: [PATCH] feat: Add support for removing deleted source files Detects files removed from the source directory, deletes the file from destination, removes it from the metadata cache. --- Program.cs | 38 ++++++++++++++++++------- Readme.md | 10 ++----- SiteFile/SiteFile.ConverterFunctions.cs | 21 +++++++------- SiteFile/SiteFile.cs | 36 +++++++++++++++++++++-- TODO | 3 -- csSiteGen.csproj | 2 +- 6 files changed, 75 insertions(+), 35 deletions(-) diff --git a/Program.cs b/Program.cs index 6121078..0da168c 100644 --- a/Program.cs +++ b/Program.cs @@ -16,10 +16,8 @@ * 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; @@ -209,7 +207,7 @@ class Program AnsiConsole.MarkupLine("[yellow]See log for more details[/]"); } } - + EnforceConsistency(settings); return 0; } @@ -253,18 +251,38 @@ class Program static void EnforceConsistency(ProjectSettings projectSettings) { + AnsiConsole.MarkupLine("[blue]Checking for stale files[/]"); // Grab the metadata. - + List staleMetadataFiles = new(); + var metadata = SiteFile.getMetadata(projectSettings); // Read the metadata. + foreach (var entry in metadata) + { + var file = entry.Key; + Log.Information("Checking if {f} has been deleted", file); + if (!File.Exists(file)) + { + Log.Information("{f} HAS been deleted", file); - // Find deleted files. - - // Figure out what the new name for those files would be. - - // Remove those files. - + // what should that files new name be. + var newFileName = Conversions.GetNewName(new FileInfo(file),projectSettings); + try{ + // Delete that file and mark the metadata for removal. + File.Delete(newFileName); + AnsiConsole.MarkupLineInterpolated($"[red]Deleted [blue]{newFileName}[/][/]"); + Log.Information("Deleted {f}",newFileName); + staleMetadataFiles.Add(file); + } + catch (Exception e) + { + Log.Error(e,"Could not delete {f}",newFileName); + } + } + SiteFile.invalidateMetadata(staleMetadataFiles,projectSettings); + } } + static ProjectSettings GetProjectSettings(DirectoryInfo? ProjectDirectory) { // TODO: implement proper error handling where file access is performed. diff --git a/Readme.md b/Readme.md index 1f27b66..3301520 100644 --- a/Readme.md +++ b/Readme.md @@ -57,8 +57,6 @@ It only considers source files for conversion if they are new or have been changed since the last run of the program. Therefore if you change the `BaseUrl` or the template file you will need to clean and convert your entire project. -Convert also does not detect deleted source files at the moment, so you -will also need to clean for that, #### clean This subcommand purges the output directory, You can of course do this @@ -85,11 +83,7 @@ unless you want to structure everything to the point you only need relative links. (I personally found this almost impossible) ## Future plans -- Automatic page generation using pre-process steps and temporary files. -E.G Contents pages, index pages etc...(DIFFICULT) +- Automatic page generation using pre-process steps and temporary files. E.G Contents pages, index pages etc...(DIFFICULT) - Detection of changes to the `BaseUrl` and any templates. (MEDIUM) -- Detection of deleted source files and removal of the destination -files.(MEDIUM) - Allow expansion of conversion operations (DIFFICULT) -- Allow customisation of filetypes that can have `%BASEURL%` replaced -(EASY-MEDIUM) +- Allow customisation of filetypes that can have `%BASEURL%` replaced (EASY-MEDIUM) diff --git a/SiteFile/SiteFile.ConverterFunctions.cs b/SiteFile/SiteFile.ConverterFunctions.cs index fdf0874..6020434 100644 --- a/SiteFile/SiteFile.ConverterFunctions.cs +++ b/SiteFile/SiteFile.ConverterFunctions.cs @@ -11,8 +11,8 @@ public static class Conversions{ /// /// A Mapping of filetype to ConvertFunc. /// - public static readonly Dictionary Mappings = new(){ - {".md", Pandoc}, + public static readonly Dictionary Mappings = new(){ + {".md", (Pandoc, ".html")}, }; private static readonly string[] StringReplaceFiletypes = { @@ -26,7 +26,7 @@ public static class Conversions{ /// public static bool NoOp(FileInfo file, ProjectSettings settings){ Log.Information("Performing NoOp Conversion"); - string newName = GetNewName(file,settings,"NoOp"); + string newName = GetNewName(file,settings); Log.Debug("{FullName} -> {newName}",file.FullName,newName); Thread.Sleep(1500); return true; @@ -36,7 +36,7 @@ public static class Conversions{ /// Copy the file verbatim (doing any baseurl replacements if needed) /// public static bool RawCpy(FileInfo file, ProjectSettings settings){ - FileInfo newPath = new FileInfo(GetNewName(file,settings,null)); + FileInfo newPath = new FileInfo(GetNewName(file,settings)); Log.Information("RawCpy: Copying {file} to {dest}",file.FullName, newPath.FullName); @@ -133,11 +133,11 @@ public static class Conversions{ // 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")}"; + pandocArgs = $"{tmpFile} -o {GetNewName(file,settings)}"; } else { - pandocArgs = $"{file.FullName} -o {GetNewName(file,settings,".html")}"; + pandocArgs = $"{file.FullName} -o {GetNewName(file,settings)}"; } if (template is not null) @@ -156,9 +156,9 @@ public static class Conversions{ // This is used in my template to add the sitename to the title element. pandocArgs += $" --metadata=\"SiteName:{settings.SiteName}\""; } - if (!Directory.Exists(Path.GetDirectoryName(GetNewName(file,settings,".html")))) + if (!Directory.Exists(Path.GetDirectoryName(GetNewName(file,settings)))) { - Directory.CreateDirectory(Path.GetDirectoryName(GetNewName(file,settings,".html"))!); + Directory.CreateDirectory(Path.GetDirectoryName(GetNewName(file,settings))!); } bool pandocReturn = RunExternalProgram(pandoc,pandocArgs); @@ -214,10 +214,11 @@ public static class Conversions{ return true; } - private static string GetNewName(FileInfo file, ProjectSettings settings, string? newExtension){ + public static string GetNewName(FileInfo file, ProjectSettings settings){ + var newExtension = Mappings.GetValueOrDefault(file.Extension,(RawCpy,file.Extension)).extension; return file.FullName .Replace(settings.InputDirectory.FullName, settings.OutputDirectory.FullName) - .Replace(file.Extension,newExtension ?? file.Extension); + .Replace(file.Extension,newExtension); } private static string StringReplace(FileInfo file, ProjectSettings settings){ diff --git a/SiteFile/SiteFile.cs b/SiteFile/SiteFile.cs index 5f5532e..88dd667 100644 --- a/SiteFile/SiteFile.cs +++ b/SiteFile/SiteFile.cs @@ -12,6 +12,36 @@ public partial class SiteFile Conversions.ConvertFunc ConverterFunction; static Dictionary? Metadata = null; + /// + /// Get the metadata for the current project, loading it if not already loaded. + /// + /// + /// The current ProjectSettings + /// + /// + /// A Dictionary that represents the metadata stored. + /// + public static Dictionary getMetadata(ProjectSettings settings) { + if (Metadata is null) + { + LoadMetadata(settings); + } + return Metadata?? new(); // Metadata is unlikely(if not impossible) to be null here but the compiler isn't convinced + } + + public static void invalidateMetadata(List files, ProjectSettings settings) + { + if (Metadata is null) + { + return; + } + files.ForEach( file => { + Metadata.Remove(file); + }); + + SaveMetadata(settings); + } + /// /// The name of the file, Not Guaranteed to be unique. /// Use only for output and logging, never file operations, @@ -32,7 +62,7 @@ public partial class SiteFile Log.Debug("{file} extension is {ext}",fileInfo.FullName, fileInfo.Extension); // Using this Ensures that the ConverterFunction is Always set. // ConverterFunctions ALWAYS accept just the FileInfo, and ProjectSettings passed at convert time. - ConverterFunction = Conversions.Mappings.GetValueOrDefault(info.Extension, Conversions.RawCpy); + ConverterFunction = Conversions.Mappings.GetValueOrDefault(info.Extension, (Conversions.RawCpy,info.Extension)).function; } /// @@ -88,7 +118,7 @@ public partial class SiteFile * But that ensures that if you remove the output directory the site will be * Fully recreated. */ - private void LoadMetadata(ProjectSettings settings) + private static void LoadMetadata(ProjectSettings settings) { string metaFile = $"{settings.OutputDirectory}/.files"; Log.Information("Loading Metadata from {file}",metaFile); @@ -114,7 +144,7 @@ public partial class SiteFile } } - private void SaveMetadata(ProjectSettings settings) + private static void SaveMetadata(ProjectSettings settings) { if (Metadata is null) { diff --git a/TODO b/TODO index 337e0d7..2e6cc54 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,2 @@ -- 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. - Ensure that the .gitkeep file is placed into Testing/dst diff --git a/csSiteGen.csproj b/csSiteGen.csproj index 0b3dde4..72d3ef2 100644 --- a/csSiteGen.csproj +++ b/csSiteGen.csproj @@ -4,7 +4,7 @@ Exe $(RELEASE_VERSION) - 0.0.2 + 0.0.3 $([System.DateTime]::UtcNow.ToString(`yyyyMMdd-HHmm`))