using System.CommandLine; using NatsNet.PortTracker.Data; namespace NatsNet.PortTracker.Commands; public static class ModuleCommands { public static Command Create(Option dbOption, Option schemaOption) { var moduleCommand = new Command("module", "Manage modules"); // list var listStatus = new Option("--status") { Description = "Filter by status" }; var listCmd = new Command("list", "List modules"); listCmd.Add(listStatus); listCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var status = parseResult.GetValue(listStatus); using var db = new Database(dbPath); var sql = "SELECT id, name, status, go_package, go_line_count, dotnet_project, dotnet_class FROM modules"; var parameters = new List<(string, object?)>(); if (status is not null) { sql += " WHERE status = @status"; parameters.Add(("@status", status)); } sql += " ORDER BY name"; var rows = db.Query(sql, parameters.ToArray()); Console.WriteLine($"{"ID",-5} {"Name",-25} {"Status",-15} {"Go Pkg",-15} {"LOC",-8} {"DotNet Project",-25} {"DotNet Class",-20}"); Console.WriteLine(new string('-', 113)); foreach (var row in rows) { Console.WriteLine($"{row["id"],-5} {row["name"],-25} {row["status"],-15} {row["go_package"],-15} {row["go_line_count"],-8} {row["dotnet_project"] ?? "",-25} {row["dotnet_class"] ?? "",-20}"); } Console.WriteLine($"\nTotal: {rows.Count} modules"); }); // show var showId = new Argument("id") { Description = "Module ID" }; var showCmd = new Command("show", "Show module details"); showCmd.Add(showId); showCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var id = parseResult.GetValue(showId); using var db = new Database(dbPath); var modules = db.Query("SELECT * FROM modules WHERE id = @id", ("@id", id)); if (modules.Count == 0) { Console.WriteLine($"Module {id} not found."); return; } var mod = modules[0]; Console.WriteLine($"Module #{mod["id"]}: {mod["name"]}"); Console.WriteLine($" Status: {mod["status"]}"); Console.WriteLine($" Go Package: {mod["go_package"]}"); Console.WriteLine($" Go File: {mod["go_file"]}"); Console.WriteLine($" Go LOC: {mod["go_line_count"]}"); Console.WriteLine($" .NET: {mod["dotnet_project"]} / {mod["dotnet_namespace"]} / {mod["dotnet_class"]}"); Console.WriteLine($" Notes: {mod["notes"]}"); var features = db.Query( "SELECT id, name, status, go_method, dotnet_method FROM features WHERE module_id = @id ORDER BY name", ("@id", id)); Console.WriteLine($"\n Features ({features.Count}):"); foreach (var f in features) Console.WriteLine($" #{f["id"],-5} {f["name"],-35} {f["status"],-15} {f["dotnet_method"] ?? ""}"); var tests = db.Query( "SELECT id, name, status, dotnet_method FROM unit_tests WHERE module_id = @id ORDER BY name", ("@id", id)); Console.WriteLine($"\n Tests ({tests.Count}):"); foreach (var t in tests) Console.WriteLine($" #{t["id"],-5} {t["name"],-35} {t["status"],-15} {t["dotnet_method"] ?? ""}"); var deps = db.Query( "SELECT d.target_type, d.target_id, d.dependency_kind, m.name as target_name FROM dependencies d LEFT JOIN modules m ON d.target_type = 'module' AND d.target_id = m.id WHERE d.source_type = 'module' AND d.source_id = @id", ("@id", id)); Console.WriteLine($"\n Dependencies ({deps.Count}):"); foreach (var d in deps) Console.WriteLine($" -> {d["target_type"]} #{d["target_id"]} ({d["target_name"]}) [{d["dependency_kind"]}]"); }); // update var updateId = new Argument("id") { Description = "Module ID" }; var updateStatus = new Option("--status") { Description = "New status", Required = true }; var updateCmd = new Command("update", "Update module status"); updateCmd.Add(updateId); updateCmd.Add(updateStatus); updateCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var id = parseResult.GetValue(updateId); var status = parseResult.GetValue(updateStatus)!; using var db = new Database(dbPath); var affected = db.Execute("UPDATE modules SET status = @status WHERE id = @id", ("@status", status), ("@id", id)); Console.WriteLine(affected > 0 ? $"Module {id} updated to '{status}'." : $"Module {id} not found."); }); // map var mapId = new Argument("id") { Description = "Module ID" }; var mapProject = new Option("--project") { Description = "Target .NET project", Required = true }; var mapNamespace = new Option("--namespace") { Description = "Target namespace" }; var mapClass = new Option("--class") { Description = "Target class" }; var mapCmd = new Command("map", "Map module to .NET project"); mapCmd.Add(mapId); mapCmd.Add(mapProject); mapCmd.Add(mapNamespace); mapCmd.Add(mapClass); mapCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var id = parseResult.GetValue(mapId); var project = parseResult.GetValue(mapProject)!; var ns = parseResult.GetValue(mapNamespace); var cls = parseResult.GetValue(mapClass); using var db = new Database(dbPath); var affected = db.Execute( "UPDATE modules SET dotnet_project = @project, dotnet_namespace = @ns, dotnet_class = @cls WHERE id = @id", ("@project", project), ("@ns", ns), ("@cls", cls), ("@id", id)); Console.WriteLine(affected > 0 ? $"Module {id} mapped to {project}." : $"Module {id} not found."); }); // set-na var naId = new Argument("id") { Description = "Module ID" }; var naReason = new Option("--reason") { Description = "Reason for N/A", Required = true }; var naCmd = new Command("set-na", "Mark module as N/A"); naCmd.Add(naId); naCmd.Add(naReason); naCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var id = parseResult.GetValue(naId); var reason = parseResult.GetValue(naReason)!; using var db = new Database(dbPath); var affected = db.Execute( "UPDATE modules SET status = 'n_a', notes = @reason WHERE id = @id", ("@reason", reason), ("@id", id)); Console.WriteLine(affected > 0 ? $"Module {id} set to N/A: {reason}" : $"Module {id} not found."); }); moduleCommand.Add(listCmd); moduleCommand.Add(showCmd); moduleCommand.Add(updateCmd); moduleCommand.Add(mapCmd); moduleCommand.Add(naCmd); moduleCommand.Add(CreateBatchUpdate(dbOption)); moduleCommand.Add(CreateBatchMap(dbOption)); return moduleCommand; } private static Command CreateBatchUpdate(Option dbOption) { var cmd = new Command("batch-update", "Bulk update module status"); var idsOpt = BatchFilters.IdsOption(); var statusOpt = BatchFilters.StatusOption(); var executeOpt = BatchFilters.ExecuteOption(); var setStatus = new Option("--set-status") { Description = "New status to set", Required = true }; var setNotes = new Option("--set-notes") { Description = "Notes to set" }; cmd.Add(idsOpt); cmd.Add(statusOpt); cmd.Add(executeOpt); cmd.Add(setStatus); cmd.Add(setNotes); cmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var ids = parseResult.GetValue(idsOpt); var status = parseResult.GetValue(statusOpt); var execute = parseResult.GetValue(executeOpt); var newStatus = parseResult.GetValue(setStatus)!; var notes = parseResult.GetValue(setNotes); if (string.IsNullOrWhiteSpace(ids) && string.IsNullOrWhiteSpace(status)) { Console.WriteLine("Error: at least one filter (--ids, --status) is required."); return; } using var db = new Database(dbPath); var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, null, status); var setClauses = new List { "status = @newStatus" }; var updateParams = new List<(string, object?)> { ("@newStatus", newStatus) }; if (notes is not null) { setClauses.Add("notes = @newNotes"); updateParams.Add(("@newNotes", notes)); } BatchFilters.PreviewOrExecute(db, "modules", "id, name, status, notes", string.Join(", ", setClauses), updateParams, whereClause, filterParams, execute); }); return cmd; } private static Command CreateBatchMap(Option dbOption) { var cmd = new Command("batch-map", "Bulk map modules to .NET projects"); var idsOpt = BatchFilters.IdsOption(); var statusOpt = BatchFilters.StatusOption(); var executeOpt = BatchFilters.ExecuteOption(); var setProject = new Option("--set-project") { Description = ".NET project" }; var setNamespace = new Option("--set-namespace") { Description = ".NET namespace" }; var setClass = new Option("--set-class") { Description = ".NET class" }; cmd.Add(idsOpt); cmd.Add(statusOpt); cmd.Add(executeOpt); cmd.Add(setProject); cmd.Add(setNamespace); cmd.Add(setClass); cmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var ids = parseResult.GetValue(idsOpt); var status = parseResult.GetValue(statusOpt); var execute = parseResult.GetValue(executeOpt); var project = parseResult.GetValue(setProject); var ns = parseResult.GetValue(setNamespace); var cls = parseResult.GetValue(setClass); if (string.IsNullOrWhiteSpace(ids) && string.IsNullOrWhiteSpace(status)) { Console.WriteLine("Error: at least one filter (--ids, --status) is required."); return; } if (project is null && ns is null && cls is null) { Console.WriteLine("Error: at least one of --set-project, --set-namespace, --set-class is required."); return; } using var db = new Database(dbPath); var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, null, status); var setClauses = new List(); var updateParams = new List<(string, object?)>(); if (project is not null) { setClauses.Add("dotnet_project = @setProject"); updateParams.Add(("@setProject", project)); } if (ns is not null) { setClauses.Add("dotnet_namespace = @setNamespace"); updateParams.Add(("@setNamespace", ns)); } if (cls is not null) { setClauses.Add("dotnet_class = @setClass"); updateParams.Add(("@setClass", cls)); } BatchFilters.PreviewOrExecute(db, "modules", "id, name, status, dotnet_project, dotnet_namespace, dotnet_class", string.Join(", ", setClauses), updateParams, whereClause, filterParams, execute); }); return cmd; } }