using System.Security.Cryptography; using System.Text; using NozCli.Repl; using Spectre.Console; namespace NozCli.Commands; public class StatusCommand : ICommand { public string Name => "status"; public string Description => "Show diff between a local folder and the server"; public string Usage => "status "; public async Task ExecuteAsync(string[] args, ReplContext ctx) { var localPath = args.FirstOrDefault(); if (localPath is null) { AnsiConsole.MarkupLine("[red]Usage: status [/]"); return; } localPath = Path.GetFullPath(localPath); if (!Directory.Exists(localPath)) { AnsiConsole.MarkupLine($"[red]Not found: {localPath}[/]"); return; } var serverMap = ctx.NoteCache.ToDictionary(n => n.Slug, n => n.Hash ?? ""); var localFiles = Directory.EnumerateFiles(localPath, "*.md", SearchOption.AllDirectories) .Where(f => !Path.GetFileName(f).StartsWith("_")); var added = new List(); var modified = new List(); foreach (var file in localFiles) { var slug = Path.GetRelativePath(localPath, file).Replace(Path.DirectorySeparatorChar, '/').Replace(".md", ""); var markdown = await File.ReadAllTextAsync(file); var localHash = Sha256(markdown); if (!serverMap.ContainsKey(slug)) added.Add(slug); else if (serverMap[slug] != localHash) modified.Add(slug); } if (added.Count == 0 && modified.Count == 0) { AnsiConsole.MarkupLine("[green]✓ Everything up to date[/]"); return; } foreach (var s in added) AnsiConsole.MarkupLine($" [green]+[/] {s}"); foreach (var s in modified) AnsiConsole.MarkupLine($" [yellow]~[/] {s}"); AnsiConsole.MarkupLine($"\n[dim]{added.Count} new · {modified.Count} modified[/]"); } private static string Sha256(string text) => Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(text))).ToLower(); }