using System.CommandLine; using NatsNet.PortTracker.Data; namespace NatsNet.PortTracker.Commands; public static class DependencyCommands { public static Command Create(Option dbOption, Option schemaOption) { var depCommand = new Command("dependency", "Manage dependencies"); // show var showType = new Argument("type") { Description = "Item type (module, feature, unit_test)" }; var showId = new Argument("id") { Description = "Item ID" }; var showCmd = new Command("show", "Show dependencies for an item"); showCmd.Add(showType); showCmd.Add(showId); showCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var type = parseResult.GetValue(showType)!; var id = parseResult.GetValue(showId); using var db = new Database(dbPath); var deps = db.Query( "SELECT target_type, target_id, dependency_kind FROM dependencies WHERE source_type = @type AND source_id = @id", ("@type", type), ("@id", id)); Console.WriteLine($"Dependencies of {type} #{id} ({deps.Count}):"); foreach (var d in deps) Console.WriteLine($" -> {d["target_type"]} #{d["target_id"]} [{d["dependency_kind"]}]"); var rdeps = db.Query( "SELECT source_type, source_id, dependency_kind FROM dependencies WHERE target_type = @type AND target_id = @id", ("@type", type), ("@id", id)); Console.WriteLine($"\nReverse dependencies (depends on {type} #{id}) ({rdeps.Count}):"); foreach (var d in rdeps) Console.WriteLine($" <- {d["source_type"]} #{d["source_id"]} [{d["dependency_kind"]}]"); }); // blocked var blockedCmd = new Command("blocked", "Show items blocked by unported dependencies"); blockedCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; using var db = new Database(dbPath); var sql = @" SELECT 'module' as item_type, m.id, m.name, m.status, d.target_type, d.target_id, d.dependency_kind FROM modules m JOIN dependencies d ON d.source_type = 'module' AND d.source_id = m.id WHERE m.status NOT IN ('complete', 'verified', 'n_a') AND ( (d.target_type = 'module' AND d.target_id IN (SELECT id FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a'))) OR (d.target_type = 'feature' AND d.target_id IN (SELECT id FROM features WHERE status NOT IN ('complete', 'verified', 'n_a'))) OR (d.target_type = 'unit_test' AND d.target_id IN (SELECT id FROM unit_tests WHERE status NOT IN ('complete', 'verified', 'n_a'))) ) UNION ALL SELECT 'feature' as item_type, f.id, f.name, f.status, d.target_type, d.target_id, d.dependency_kind FROM features f JOIN dependencies d ON d.source_type = 'feature' AND d.source_id = f.id WHERE f.status NOT IN ('complete', 'verified', 'n_a') AND ( (d.target_type = 'module' AND d.target_id IN (SELECT id FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a'))) OR (d.target_type = 'feature' AND d.target_id IN (SELECT id FROM features WHERE status NOT IN ('complete', 'verified', 'n_a'))) OR (d.target_type = 'unit_test' AND d.target_id IN (SELECT id FROM unit_tests WHERE status NOT IN ('complete', 'verified', 'n_a'))) ) ORDER BY 1, 2"; var rows = db.Query(sql); if (rows.Count == 0) { Console.WriteLine("No blocked items found."); return; } Console.WriteLine($"{"Type",-10} {"ID",-5} {"Name",-30} {"Status",-15} {"Blocked By",-15} {"Dep ID",-8} {"Kind",-10}"); Console.WriteLine(new string('-', 93)); foreach (var row in rows) { Console.WriteLine($"{row["item_type"],-10} {row["id"],-5} {Truncate(row["name"]?.ToString(), 29),-30} {row["status"],-15} {row["target_type"],-15} {row["target_id"],-8} {row["dependency_kind"],-10}"); } Console.WriteLine($"\n{rows.Count} blocking relationships found."); }); // ready var readyCmd = new Command("ready", "Show items ready to port (no unported dependencies)"); readyCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; using var db = new Database(dbPath); var sql = @" SELECT 'module' as item_type, m.id, m.name, m.status FROM modules m WHERE m.status IN ('not_started', 'stub') AND NOT EXISTS ( SELECT 1 FROM dependencies d WHERE d.source_type = 'module' AND d.source_id = m.id AND ( (d.target_type = 'module' AND d.target_id IN (SELECT id FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a'))) OR (d.target_type = 'feature' AND d.target_id IN (SELECT id FROM features WHERE status NOT IN ('complete', 'verified', 'n_a'))) OR (d.target_type = 'unit_test' AND d.target_id IN (SELECT id FROM unit_tests WHERE status NOT IN ('complete', 'verified', 'n_a'))) ) ) UNION ALL SELECT 'feature' as item_type, f.id, f.name, f.status FROM features f WHERE f.status IN ('not_started', 'stub') AND NOT EXISTS ( SELECT 1 FROM dependencies d WHERE d.source_type = 'feature' AND d.source_id = f.id AND ( (d.target_type = 'module' AND d.target_id IN (SELECT id FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a'))) OR (d.target_type = 'feature' AND d.target_id IN (SELECT id FROM features WHERE status NOT IN ('complete', 'verified', 'n_a'))) OR (d.target_type = 'unit_test' AND d.target_id IN (SELECT id FROM unit_tests WHERE status NOT IN ('complete', 'verified', 'n_a'))) ) ) ORDER BY 1, 2"; var rows = db.Query(sql); if (rows.Count == 0) { Console.WriteLine("No items are ready to port (all items either have unported deps or are already done)."); return; } Console.WriteLine($"{"Type",-10} {"ID",-5} {"Name",-40} {"Status",-15}"); Console.WriteLine(new string('-', 70)); foreach (var row in rows) { Console.WriteLine($"{row["item_type"],-10} {row["id"],-5} {Truncate(row["name"]?.ToString(), 39),-40} {row["status"],-15}"); } Console.WriteLine($"\n{rows.Count} items ready to port."); }); depCommand.Add(showCmd); depCommand.Add(blockedCmd); depCommand.Add(readyCmd); return depCommand; } private static string Truncate(string? s, int maxLen) { if (s is null) return ""; return s.Length <= maxLen ? s : s[..(maxLen - 2)] + ".."; } }