From 7b4483c4f2bb1eaba3230e2be8193e91cc2b9312 Mon Sep 17 00:00:00 2001 From: OGR-67 Date: Sun, 22 Mar 2026 11:13:50 +0100 Subject: [PATCH 1/2] feat: Add remove hook command and refactor hook handling --- src/AL2DBML.CLI/Commands/InitCommand.cs | 55 ++++------------ src/AL2DBML.CLI/Commands/RemoveHookCommand.cs | 29 +++++++++ src/AL2DBML.CLI/Constants/HookMarkers.cs | 7 ++ src/AL2DBML.CLI/Program.cs | 1 + src/AL2DBML.CLI/Services/HookService.cs | 65 +++++++++++++++++++ 5 files changed, 115 insertions(+), 42 deletions(-) create mode 100644 src/AL2DBML.CLI/Commands/RemoveHookCommand.cs create mode 100644 src/AL2DBML.CLI/Constants/HookMarkers.cs create mode 100644 src/AL2DBML.CLI/Services/HookService.cs diff --git a/src/AL2DBML.CLI/Commands/InitCommand.cs b/src/AL2DBML.CLI/Commands/InitCommand.cs index 6d856dd..03d5863 100644 --- a/src/AL2DBML.CLI/Commands/InitCommand.cs +++ b/src/AL2DBML.CLI/Commands/InitCommand.cs @@ -1,3 +1,4 @@ +using AL2DBML.CLI.Constants; using AL2DBML.CLI.Models; using AL2DBML.CLI.Services; using Spectre.Console; @@ -11,9 +12,6 @@ public class InitSettings : CommandSettings public class InitCommand : AsyncCommand { - private const string HookStartMarker = "# [al2dbml-start]"; - private const string HookEndMarker = "# [al2dbml-end]"; - private const string HookCommand = "al2dbml generate"; private const string GitignoreEntry = ".al2dbml/config.local.json"; private readonly IConfigService _configService; @@ -44,7 +42,7 @@ protected override Task ExecuteAsync(CommandContext context, InitSettings s .DefaultValue(existingShared?.Output.Name ?? "schema")); var hookExists = File.Exists(".git/hooks/pre-commit") && - File.ReadAllText(".git/hooks/pre-commit").Contains(HookStartMarker, StringComparison.Ordinal); + File.ReadAllText(".git/hooks/pre-commit").Contains(HookMarkers.Start, StringComparison.Ordinal); var createHook = AnsiConsole.Confirm("Create a pre-commit hook?", hookExists); _configService.SaveSharedConfig(new SharedConfig @@ -59,7 +57,17 @@ protected override Task ExecuteAsync(CommandContext context, InitSettings s EnsureGitignoreEntry(GitignoreEntry); if (createHook) - WritePreCommitHook(); + { + if (!Directory.Exists(".git/hooks")) + AnsiConsole.MarkupLine("[yellow]Warning:[/] .git/hooks directory not found — skipping pre-commit hook creation."); + else + HookService.Write(); + } + else if (hookExists) + { + AnsiConsole.MarkupLine("[yellow]Removing existing pre-commit hook...[/]"); + HookService.Remove(); + } AnsiConsole.MarkupLine("[green]Done:[/] AL2DBML initialized."); return Task.FromResult(0); @@ -78,41 +86,4 @@ private static void EnsureGitignoreEntry(string entry) lines.Add(entry); File.WriteAllLines(gitignorePath, lines); } - - private static void WritePreCommitHook() - { - const string hookPath = ".git/hooks/pre-commit"; - - if (!Directory.Exists(".git/hooks")) - { - AnsiConsole.MarkupLine("[yellow]Warning:[/] .git/hooks directory not found — skipping pre-commit hook creation."); - return; - } - var hookSection = $"{HookStartMarker}\nif command -v al2dbml > /dev/null 2>&1; then\n {HookCommand} || printf \"\\033[33mWarning: al2dbml generate failed, skipping DBML update.\\033[0m\\n\"\nelse\n printf \"\\033[33mWarning: al2dbml not found, skipping DBML update.\\033[0m\\n\"\nfi\n{HookEndMarker}"; - - string content; - if (File.Exists(hookPath)) - { - content = File.ReadAllText(hookPath); - var startIdx = content.IndexOf(HookStartMarker, StringComparison.Ordinal); - var endIdx = content.IndexOf(HookEndMarker, StringComparison.Ordinal); - - if (startIdx >= 0 && endIdx >= 0) - content = content[..startIdx] + hookSection + content[(endIdx + HookEndMarker.Length)..]; - else - content = content.TrimEnd() + $"\n\n{hookSection}\n"; - } - else - { - content = $"#!/bin/sh\n\n{hookSection}\n"; - } - - File.WriteAllText(hookPath, content); - - if (!OperatingSystem.IsWindows()) - File.SetUnixFileMode(hookPath, - UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | - UnixFileMode.GroupRead | UnixFileMode.GroupExecute | - UnixFileMode.OtherRead | UnixFileMode.OtherExecute); - } } diff --git a/src/AL2DBML.CLI/Commands/RemoveHookCommand.cs b/src/AL2DBML.CLI/Commands/RemoveHookCommand.cs new file mode 100644 index 0000000..37b4f5b --- /dev/null +++ b/src/AL2DBML.CLI/Commands/RemoveHookCommand.cs @@ -0,0 +1,29 @@ +using AL2DBML.CLI.Services; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace AL2DBML.CLI.Commands; + +public class RemoveHookSettings : CommandSettings +{ +} + +public class RemoveHookCommand : Command +{ + protected override int Execute(CommandContext context, RemoveHookSettings settings, CancellationToken cancellationToken) + { + if (!File.Exists(".git/hooks/pre-commit")) + { + AnsiConsole.MarkupLine("[yellow]Warning:[/] No pre-commit hook found."); + return 0; + } + + var removed = HookService.Remove(); + if (!removed) + AnsiConsole.MarkupLine("[yellow]Warning:[/] No AL2DBML section found in pre-commit hook."); + else + AnsiConsole.MarkupLine("[green]Done:[/] AL2DBML section removed from pre-commit hook."); + + return 0; + } +} diff --git a/src/AL2DBML.CLI/Constants/HookMarkers.cs b/src/AL2DBML.CLI/Constants/HookMarkers.cs new file mode 100644 index 0000000..38f62d4 --- /dev/null +++ b/src/AL2DBML.CLI/Constants/HookMarkers.cs @@ -0,0 +1,7 @@ +namespace AL2DBML.CLI.Constants; + +internal static class HookMarkers +{ + public const string Start = "# [al2dbml-start]"; + public const string End = "# [al2dbml-end]"; +} diff --git a/src/AL2DBML.CLI/Program.cs b/src/AL2DBML.CLI/Program.cs index 598714a..3ff1e4f 100644 --- a/src/AL2DBML.CLI/Program.cs +++ b/src/AL2DBML.CLI/Program.cs @@ -23,6 +23,7 @@ { config.AddCommand("generate"); config.AddCommand("init"); + config.AddCommand("remove-hook"); }); return await app.RunAsync(args); diff --git a/src/AL2DBML.CLI/Services/HookService.cs b/src/AL2DBML.CLI/Services/HookService.cs new file mode 100644 index 0000000..d3a3032 --- /dev/null +++ b/src/AL2DBML.CLI/Services/HookService.cs @@ -0,0 +1,65 @@ +using AL2DBML.CLI.Constants; + +namespace AL2DBML.CLI.Services; + +internal static class HookService +{ + private const string HookPath = ".git/hooks/pre-commit"; + private const string HookCommand = "al2dbml generate"; + + public static void Write() + { + if (!Directory.Exists(".git/hooks")) + return; + + var hookSection = $"{HookMarkers.Start}\nif command -v al2dbml > /dev/null 2>&1; then\n {HookCommand} || printf \"\\033[33mWarning: al2dbml generate failed, skipping DBML update.\\033[0m\\n\"\nelse\n printf \"\\033[33mWarning: al2dbml not found, skipping DBML update.\\033[0m\\n\"\nfi\n{HookMarkers.End}"; + + string content; + if (File.Exists(HookPath)) + { + content = File.ReadAllText(HookPath); + var startIdx = content.IndexOf(HookMarkers.Start, StringComparison.Ordinal); + var endIdx = content.IndexOf(HookMarkers.End, StringComparison.Ordinal); + + if (startIdx >= 0 && endIdx >= 0) + content = content[..startIdx] + hookSection + content[(endIdx + HookMarkers.End.Length)..]; + else + content = content.TrimEnd() + $"\n\n{hookSection}\n"; + } + else + { + content = $"#!/bin/sh\n\n{hookSection}\n"; + } + + File.WriteAllText(HookPath, content); + + if (!OperatingSystem.IsWindows()) + File.SetUnixFileMode(HookPath, + UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | + UnixFileMode.GroupRead | UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | UnixFileMode.OtherExecute); + } + + public static bool Remove() + { + if (!File.Exists(HookPath)) return false; + + var content = File.ReadAllText(HookPath); + var startIdx = content.IndexOf(HookMarkers.Start, StringComparison.Ordinal); + var endIdx = content.IndexOf(HookMarkers.End, StringComparison.Ordinal); + if (startIdx < 0 || endIdx < 0) return false; + + var before = content[..startIdx].TrimEnd(); + var after = content[(endIdx + HookMarkers.End.Length)..].TrimStart('\r', '\n'); + var newContent = before.Length > 0 && after.Length > 0 + ? before + "\n\n" + after + : (before + after).Trim(); + + if (string.IsNullOrWhiteSpace(newContent.Replace("#!/bin/sh", ""))) + File.Delete(HookPath); + else + File.WriteAllText(HookPath, newContent + "\n"); + + return true; + } +} From 24c2de18b7b6e81970163dc868bc67774ac6197d Mon Sep 17 00:00:00 2001 From: OGR-67 Date: Sun, 22 Mar 2026 11:27:26 +0100 Subject: [PATCH 2/2] feat: Refactor RemoveHookCommand and update HookService logic --- src/AL2DBML.CLI/Commands/RemoveHookCommand.cs | 6 ------ src/AL2DBML.CLI/Program.cs | 1 + src/AL2DBML.CLI/Services/HookService.cs | 4 ++-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/AL2DBML.CLI/Commands/RemoveHookCommand.cs b/src/AL2DBML.CLI/Commands/RemoveHookCommand.cs index 37b4f5b..e8c7c6c 100644 --- a/src/AL2DBML.CLI/Commands/RemoveHookCommand.cs +++ b/src/AL2DBML.CLI/Commands/RemoveHookCommand.cs @@ -12,12 +12,6 @@ public class RemoveHookCommand : Command { protected override int Execute(CommandContext context, RemoveHookSettings settings, CancellationToken cancellationToken) { - if (!File.Exists(".git/hooks/pre-commit")) - { - AnsiConsole.MarkupLine("[yellow]Warning:[/] No pre-commit hook found."); - return 0; - } - var removed = HookService.Remove(); if (!removed) AnsiConsole.MarkupLine("[yellow]Warning:[/] No AL2DBML section found in pre-commit hook."); diff --git a/src/AL2DBML.CLI/Program.cs b/src/AL2DBML.CLI/Program.cs index 3ff1e4f..f329f02 100644 --- a/src/AL2DBML.CLI/Program.cs +++ b/src/AL2DBML.CLI/Program.cs @@ -12,6 +12,7 @@ .AddAL2Dbml() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped(); diff --git a/src/AL2DBML.CLI/Services/HookService.cs b/src/AL2DBML.CLI/Services/HookService.cs index d3a3032..9fb116b 100644 --- a/src/AL2DBML.CLI/Services/HookService.cs +++ b/src/AL2DBML.CLI/Services/HookService.cs @@ -21,7 +21,7 @@ public static void Write() var startIdx = content.IndexOf(HookMarkers.Start, StringComparison.Ordinal); var endIdx = content.IndexOf(HookMarkers.End, StringComparison.Ordinal); - if (startIdx >= 0 && endIdx >= 0) + if (startIdx >= 0 && endIdx > startIdx) content = content[..startIdx] + hookSection + content[(endIdx + HookMarkers.End.Length)..]; else content = content.TrimEnd() + $"\n\n{hookSection}\n"; @@ -47,7 +47,7 @@ public static bool Remove() var content = File.ReadAllText(HookPath); var startIdx = content.IndexOf(HookMarkers.Start, StringComparison.Ordinal); var endIdx = content.IndexOf(HookMarkers.End, StringComparison.Ordinal); - if (startIdx < 0 || endIdx < 0) return false; + if (startIdx < 0 || endIdx <= startIdx) return false; var before = content[..startIdx].TrimEnd(); var after = content[(endIdx + HookMarkers.End.Length)..].TrimStart('\r', '\n');