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.
This commit is contained in:
Robert Morrison 2025-03-12 02:31:54 +00:00
parent 264129b1e9
commit da24073f14
Signed by: robert
GPG Key ID: 73E012EB3F4EC696
6 changed files with 75 additions and 35 deletions

View File

@ -16,10 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<string> 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.

View File

@ -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)

View File

@ -11,8 +11,8 @@ public static class Conversions{
/// <summary>
/// A Mapping of filetype to ConvertFunc.
/// </summary>
public static readonly Dictionary<string,ConvertFunc> Mappings = new(){
{".md", Pandoc},
public static readonly Dictionary<string,(ConvertFunc function,string extension)> Mappings = new(){
{".md", (Pandoc, ".html")},
};
private static readonly string[] StringReplaceFiletypes = {
@ -26,7 +26,7 @@ public static class Conversions{
/// </summary>
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)
/// </summary>
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){

View File

@ -12,6 +12,36 @@ public partial class SiteFile
Conversions.ConvertFunc ConverterFunction;
static Dictionary<string, DateTime>? Metadata = null;
/// <summary>
/// Get the metadata for the current project, loading it if not already loaded.
/// </summary>
/// <param name="settings">
/// The current <c>ProjectSettings</c>
/// </param>
/// <returns>
/// A <c>Dictionary<string,DateTime></c> that represents the metadata stored.
/// </returns>
public static Dictionary<string,DateTime> 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<string> files, ProjectSettings settings)
{
if (Metadata is null)
{
return;
}
files.ForEach( file => {
Metadata.Remove(file);
});
SaveMetadata(settings);
}
/// <summary>
/// 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;
}
/// <summary>
@ -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)
{

3
TODO
View File

@ -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

View File

@ -4,7 +4,7 @@
<OutputType>Exe</OutputType>
<Version Condition="'$(RELEASE_VERSION)' != ''">$(RELEASE_VERSION)</Version>
<VersionPrefix Condition="'$(RELEASE_VERSION)' == ''">0.0.2</VersionPrefix>
<VersionPrefix Condition="'$(RELEASE_VERSION)' == ''">0.0.3</VersionPrefix>
<VersionSuffix Condition="'$(RELEASE_VERSION)' == ''">$([System.DateTime]::UtcNow.ToString(`yyyyMMdd-HHmm`))</VersionSuffix>