using System.CommandLine; using NatsNet.PortTracker.Data; namespace NatsNet.PortTracker.Commands; public static class LibraryCommands { public static Command Create(Option dbOption, Option schemaOption) { var libraryCommand = new Command("library", "Manage library mappings"); // list var listStatus = new Option("--status") { Description = "Filter by status" }; var listCmd = new Command("list", "List library mappings"); 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, go_import_path, go_library_name, dotnet_package, dotnet_namespace, status FROM library_mappings"; var parameters = new List<(string, object?)>(); if (status is not null) { sql += " WHERE status = @status"; parameters.Add(("@status", status)); } sql += " ORDER BY go_import_path"; var rows = db.Query(sql, parameters.ToArray()); Console.WriteLine($"{"ID",-5} {"Go Import Path",-40} {"Go Library",-20} {"DotNet Package",-25} {"DotNet Namespace",-25} {"Status",-12}"); Console.WriteLine(new string('-', 127)); foreach (var row in rows) { Console.WriteLine($"{row["id"],-5} {Truncate(row["go_import_path"]?.ToString(), 39),-40} {Truncate(row["go_library_name"]?.ToString(), 19),-20} {Truncate(row["dotnet_package"]?.ToString(), 24),-25} {Truncate(row["dotnet_namespace"]?.ToString(), 24),-25} {row["status"],-12}"); } Console.WriteLine($"\nTotal: {rows.Count} library mappings"); }); // map var mapId = new Argument("id") { Description = "Library mapping ID" }; var mapPackage = new Option("--package") { Description = ".NET NuGet package" }; var mapNamespace = new Option("--namespace") { Description = ".NET namespace" }; var mapNotes = new Option("--notes") { Description = "Usage notes" }; var mapCmd = new Command("map", "Map Go library to .NET package"); mapCmd.Add(mapId); mapCmd.Add(mapPackage); mapCmd.Add(mapNamespace); mapCmd.Add(mapNotes); mapCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; var id = parseResult.GetValue(mapId); var package = parseResult.GetValue(mapPackage); var ns = parseResult.GetValue(mapNamespace); var notes = parseResult.GetValue(mapNotes); using var db = new Database(dbPath); var affected = db.Execute( "UPDATE library_mappings SET dotnet_package = COALESCE(@package, dotnet_package), dotnet_namespace = COALESCE(@ns, dotnet_namespace), dotnet_usage_notes = COALESCE(@notes, dotnet_usage_notes), status = 'mapped' WHERE id = @id", ("@package", package), ("@ns", ns), ("@notes", notes), ("@id", id)); Console.WriteLine(affected > 0 ? $"Library {id} mapped." : $"Library {id} not found."); }); // suggest var suggestCmd = new Command("suggest", "Show unmapped libraries"); suggestCmd.SetAction(parseResult => { var dbPath = parseResult.GetValue(dbOption)!; using var db = new Database(dbPath); var rows = db.Query( "SELECT id, go_import_path, go_library_name, go_usage_description FROM library_mappings WHERE status = 'not_mapped' ORDER BY go_import_path"); if (rows.Count == 0) { Console.WriteLine("All libraries have been mapped!"); return; } Console.WriteLine($"{"ID",-5} {"Go Import Path",-45} {"Library",-20} {"Usage",-40}"); Console.WriteLine(new string('-', 110)); foreach (var row in rows) { Console.WriteLine($"{row["id"],-5} {Truncate(row["go_import_path"]?.ToString(), 44),-45} {Truncate(row["go_library_name"]?.ToString(), 19),-20} {Truncate(row["go_usage_description"]?.ToString(), 39),-40}"); } Console.WriteLine($"\n{rows.Count} unmapped libraries need attention."); }); libraryCommand.Add(listCmd); libraryCommand.Add(mapCmd); libraryCommand.Add(suggestCmd); libraryCommand.Add(CreateBatchUpdate(dbOption)); libraryCommand.Add(CreateBatchMap(dbOption)); return libraryCommand; } private static Command CreateBatchUpdate(Option dbOption) { var cmd = new Command("batch-update", "Bulk update library 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 = "Usage 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("dotnet_usage_notes = @newNotes"); updateParams.Add(("@newNotes", notes)); } BatchFilters.PreviewOrExecute(db, "library_mappings", "id, go_import_path, status, dotnet_usage_notes", string.Join(", ", setClauses), updateParams, whereClause, filterParams, execute); }); return cmd; } private static Command CreateBatchMap(Option dbOption) { var cmd = new Command("batch-map", "Bulk map libraries to .NET packages"); var idsOpt = BatchFilters.IdsOption(); var statusOpt = BatchFilters.StatusOption(); var executeOpt = BatchFilters.ExecuteOption(); var setPackage = new Option("--set-package") { Description = ".NET NuGet package" }; var setNamespace = new Option("--set-namespace") { Description = ".NET namespace" }; var setNotes = new Option("--set-notes") { Description = "Usage notes" }; cmd.Add(idsOpt); cmd.Add(statusOpt); cmd.Add(executeOpt); cmd.Add(setPackage); cmd.Add(setNamespace); 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 package = parseResult.GetValue(setPackage); var ns = parseResult.GetValue(setNamespace); var notes = parseResult.GetValue(setNotes); if (string.IsNullOrWhiteSpace(ids) && string.IsNullOrWhiteSpace(status)) { Console.WriteLine("Error: at least one filter (--ids, --status) is required."); return; } if (package is null && ns is null && notes is null) { Console.WriteLine("Error: at least one of --set-package, --set-namespace, --set-notes 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 (package is not null) { setClauses.Add("dotnet_package = @setPackage"); updateParams.Add(("@setPackage", package)); } if (ns is not null) { setClauses.Add("dotnet_namespace = @setNamespace"); updateParams.Add(("@setNamespace", ns)); } if (notes is not null) { setClauses.Add("dotnet_usage_notes = @setNotes"); updateParams.Add(("@setNotes", notes)); } BatchFilters.PreviewOrExecute(db, "library_mappings", "id, go_import_path, status, dotnet_package, dotnet_namespace", string.Join(", ", setClauses), updateParams, whereClause, filterParams, execute); }); return cmd; } private static string Truncate(string? s, int maxLen) { if (s is null) return ""; return s.Length <= maxLen ? s : s[..(maxLen - 2)] + ".."; } }