feat(porttracker): add all remaining commands (feature, test, library, dependency, report, phase)
This commit is contained in:
146
tools/NatsNet.PortTracker/Commands/DependencyCommands.cs
Normal file
146
tools/NatsNet.PortTracker/Commands/DependencyCommands.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System.CommandLine;
|
||||
using NatsNet.PortTracker.Data;
|
||||
|
||||
namespace NatsNet.PortTracker.Commands;
|
||||
|
||||
public static class DependencyCommands
|
||||
{
|
||||
public static Command Create(Option<string> dbOption, Option<string> schemaOption)
|
||||
{
|
||||
var depCommand = new Command("dependency", "Manage dependencies");
|
||||
|
||||
// show
|
||||
var showType = new Argument<string>("type") { Description = "Item type (module, feature, unit_test)" };
|
||||
var showId = new Argument<int>("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)] + "..";
|
||||
}
|
||||
}
|
||||
183
tools/NatsNet.PortTracker/Commands/FeatureCommands.cs
Normal file
183
tools/NatsNet.PortTracker/Commands/FeatureCommands.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System.CommandLine;
|
||||
using NatsNet.PortTracker.Data;
|
||||
|
||||
namespace NatsNet.PortTracker.Commands;
|
||||
|
||||
public static class FeatureCommands
|
||||
{
|
||||
public static Command Create(Option<string> dbOption, Option<string> schemaOption)
|
||||
{
|
||||
var featureCommand = new Command("feature", "Manage features");
|
||||
|
||||
// list
|
||||
var listModule = new Option<int?>("--module") { Description = "Filter by module ID" };
|
||||
var listStatus = new Option<string?>("--status") { Description = "Filter by status" };
|
||||
var listCmd = new Command("list", "List features");
|
||||
listCmd.Add(listModule);
|
||||
listCmd.Add(listStatus);
|
||||
listCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var moduleId = parseResult.GetValue(listModule);
|
||||
var status = parseResult.GetValue(listStatus);
|
||||
using var db = new Database(dbPath);
|
||||
var sql = "SELECT f.id, f.name, f.status, f.module_id, m.name as module_name, f.go_method, f.dotnet_method FROM features f LEFT JOIN modules m ON f.module_id = m.id";
|
||||
var parameters = new List<(string, object?)>();
|
||||
var clauses = new List<string>();
|
||||
if (moduleId is not null)
|
||||
{
|
||||
clauses.Add("f.module_id = @module");
|
||||
parameters.Add(("@module", moduleId));
|
||||
}
|
||||
if (status is not null)
|
||||
{
|
||||
clauses.Add("f.status = @status");
|
||||
parameters.Add(("@status", status));
|
||||
}
|
||||
if (clauses.Count > 0)
|
||||
sql += " WHERE " + string.Join(" AND ", clauses);
|
||||
sql += " ORDER BY m.name, f.name";
|
||||
|
||||
var rows = db.Query(sql, parameters.ToArray());
|
||||
Console.WriteLine($"{"ID",-5} {"Name",-30} {"Status",-15} {"Module",-20} {"Go Method",-25} {"DotNet Method",-25}");
|
||||
Console.WriteLine(new string('-', 120));
|
||||
foreach (var row in rows)
|
||||
{
|
||||
Console.WriteLine($"{row["id"],-5} {Truncate(row["name"]?.ToString(), 29),-30} {row["status"],-15} {Truncate(row["module_name"]?.ToString(), 19),-20} {Truncate(row["go_method"]?.ToString(), 24),-25} {Truncate(row["dotnet_method"]?.ToString(), 24),-25}");
|
||||
}
|
||||
Console.WriteLine($"\nTotal: {rows.Count} features");
|
||||
});
|
||||
|
||||
// show
|
||||
var showId = new Argument<int>("id") { Description = "Feature ID" };
|
||||
var showCmd = new Command("show", "Show feature details");
|
||||
showCmd.Add(showId);
|
||||
showCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var id = parseResult.GetValue(showId);
|
||||
using var db = new Database(dbPath);
|
||||
var features = db.Query(
|
||||
"SELECT f.*, m.name as module_name FROM features f LEFT JOIN modules m ON f.module_id = m.id WHERE f.id = @id",
|
||||
("@id", id));
|
||||
if (features.Count == 0)
|
||||
{
|
||||
Console.WriteLine($"Feature {id} not found.");
|
||||
return;
|
||||
}
|
||||
var f = features[0];
|
||||
Console.WriteLine($"Feature #{f["id"]}: {f["name"]}");
|
||||
Console.WriteLine($" Module: #{f["module_id"]} ({f["module_name"]})");
|
||||
Console.WriteLine($" Status: {f["status"]}");
|
||||
Console.WriteLine($" Go File: {f["go_file"]}");
|
||||
Console.WriteLine($" Go Class: {f["go_class"]}");
|
||||
Console.WriteLine($" Go Method: {f["go_method"]}");
|
||||
Console.WriteLine($" Go Line: {f["go_line_number"]}");
|
||||
Console.WriteLine($" Go LOC: {f["go_line_count"]}");
|
||||
Console.WriteLine($" .NET: {f["dotnet_project"]} / {f["dotnet_class"]} / {f["dotnet_method"]}");
|
||||
Console.WriteLine($" Notes: {f["notes"]}");
|
||||
|
||||
var deps = db.Query(
|
||||
"SELECT d.target_type, d.target_id, d.dependency_kind FROM dependencies d WHERE d.source_type = 'feature' 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["dependency_kind"]}]");
|
||||
|
||||
var rdeps = db.Query(
|
||||
"SELECT d.source_type, d.source_id, d.dependency_kind FROM dependencies d WHERE d.target_type = 'feature' AND d.target_id = @id",
|
||||
("@id", id));
|
||||
Console.WriteLine($"\n Reverse Dependencies ({rdeps.Count}):");
|
||||
foreach (var d in rdeps)
|
||||
Console.WriteLine($" <- {d["source_type"]} #{d["source_id"]} [{d["dependency_kind"]}]");
|
||||
});
|
||||
|
||||
// update
|
||||
var updateId = new Argument<int>("id") { Description = "Feature ID (use 0 with --all-in-module)" };
|
||||
var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
|
||||
var updateAllInModule = new Option<int?>("--all-in-module") { Description = "Update all features in this module ID" };
|
||||
var updateCmd = new Command("update", "Update feature status");
|
||||
updateCmd.Add(updateId);
|
||||
updateCmd.Add(updateStatus);
|
||||
updateCmd.Add(updateAllInModule);
|
||||
updateCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var id = parseResult.GetValue(updateId);
|
||||
var status = parseResult.GetValue(updateStatus)!;
|
||||
var allInModule = parseResult.GetValue(updateAllInModule);
|
||||
using var db = new Database(dbPath);
|
||||
|
||||
if (allInModule is not null)
|
||||
{
|
||||
var affected = db.Execute(
|
||||
"UPDATE features SET status = @status WHERE module_id = @module",
|
||||
("@status", status), ("@module", allInModule));
|
||||
Console.WriteLine($"Updated {affected} features in module {allInModule} to '{status}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var affected = db.Execute(
|
||||
"UPDATE features SET status = @status WHERE id = @id",
|
||||
("@status", status), ("@id", id));
|
||||
Console.WriteLine(affected > 0 ? $"Feature {id} updated to '{status}'." : $"Feature {id} not found.");
|
||||
}
|
||||
});
|
||||
|
||||
// map
|
||||
var mapId = new Argument<int>("id") { Description = "Feature ID" };
|
||||
var mapProject = new Option<string?>("--project") { Description = "Target .NET project" };
|
||||
var mapClass = new Option<string?>("--class") { Description = "Target .NET class" };
|
||||
var mapMethod = new Option<string?>("--method") { Description = "Target .NET method" };
|
||||
var mapCmd = new Command("map", "Map feature to .NET method");
|
||||
mapCmd.Add(mapId);
|
||||
mapCmd.Add(mapProject);
|
||||
mapCmd.Add(mapClass);
|
||||
mapCmd.Add(mapMethod);
|
||||
mapCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var id = parseResult.GetValue(mapId);
|
||||
var project = parseResult.GetValue(mapProject);
|
||||
var cls = parseResult.GetValue(mapClass);
|
||||
var method = parseResult.GetValue(mapMethod);
|
||||
using var db = new Database(dbPath);
|
||||
var affected = db.Execute(
|
||||
"UPDATE features SET dotnet_project = COALESCE(@project, dotnet_project), dotnet_class = COALESCE(@cls, dotnet_class), dotnet_method = COALESCE(@method, dotnet_method) WHERE id = @id",
|
||||
("@project", project), ("@cls", cls), ("@method", method), ("@id", id));
|
||||
Console.WriteLine(affected > 0 ? $"Feature {id} mapped." : $"Feature {id} not found.");
|
||||
});
|
||||
|
||||
// set-na
|
||||
var naId = new Argument<int>("id") { Description = "Feature ID" };
|
||||
var naReason = new Option<string>("--reason") { Description = "Reason for N/A", Required = true };
|
||||
var naCmd = new Command("set-na", "Mark feature 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 features SET status = 'n_a', notes = @reason WHERE id = @id",
|
||||
("@reason", reason), ("@id", id));
|
||||
Console.WriteLine(affected > 0 ? $"Feature {id} set to N/A: {reason}" : $"Feature {id} not found.");
|
||||
});
|
||||
|
||||
featureCommand.Add(listCmd);
|
||||
featureCommand.Add(showCmd);
|
||||
featureCommand.Add(updateCmd);
|
||||
featureCommand.Add(mapCmd);
|
||||
featureCommand.Add(naCmd);
|
||||
|
||||
return featureCommand;
|
||||
}
|
||||
|
||||
private static string Truncate(string? s, int maxLen)
|
||||
{
|
||||
if (s is null) return "";
|
||||
return s.Length <= maxLen ? s : s[..(maxLen - 2)] + "..";
|
||||
}
|
||||
}
|
||||
98
tools/NatsNet.PortTracker/Commands/LibraryCommands.cs
Normal file
98
tools/NatsNet.PortTracker/Commands/LibraryCommands.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System.CommandLine;
|
||||
using NatsNet.PortTracker.Data;
|
||||
|
||||
namespace NatsNet.PortTracker.Commands;
|
||||
|
||||
public static class LibraryCommands
|
||||
{
|
||||
public static Command Create(Option<string> dbOption, Option<string> schemaOption)
|
||||
{
|
||||
var libraryCommand = new Command("library", "Manage library mappings");
|
||||
|
||||
// list
|
||||
var listStatus = new Option<string?>("--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<int>("id") { Description = "Library mapping ID" };
|
||||
var mapPackage = new Option<string?>("--package") { Description = ".NET NuGet package" };
|
||||
var mapNamespace = new Option<string?>("--namespace") { Description = ".NET namespace" };
|
||||
var mapNotes = new Option<string?>("--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);
|
||||
|
||||
return libraryCommand;
|
||||
}
|
||||
|
||||
private static string Truncate(string? s, int maxLen)
|
||||
{
|
||||
if (s is null) return "";
|
||||
return s.Length <= maxLen ? s : s[..(maxLen - 2)] + "..";
|
||||
}
|
||||
}
|
||||
212
tools/NatsNet.PortTracker/Commands/PhaseCommands.cs
Normal file
212
tools/NatsNet.PortTracker/Commands/PhaseCommands.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System.CommandLine;
|
||||
using NatsNet.PortTracker.Data;
|
||||
|
||||
namespace NatsNet.PortTracker.Commands;
|
||||
|
||||
public static class PhaseCommands
|
||||
{
|
||||
private static readonly (int Number, string Name, string Description)[] Phases =
|
||||
[
|
||||
(1, "Analysis & Schema", "Run Go AST analyzer, populate DB schema, map libraries"),
|
||||
(2, "Core Infrastructure", "Port foundational modules (logging, errors, options)"),
|
||||
(3, "Message Layer", "Port message parsing, headers, protocol handling"),
|
||||
(4, "Connection Layer", "Port connection management, reconnection logic"),
|
||||
(5, "Client API", "Port publish, subscribe, request-reply"),
|
||||
(6, "Advanced Features", "Port JetStream, KV, Object Store, Services"),
|
||||
(7, "Testing & Verification", "Port and verify all unit tests, integration tests"),
|
||||
];
|
||||
|
||||
public static Command Create(Option<string> dbOption, Option<string> schemaOption)
|
||||
{
|
||||
var phaseCommand = new Command("phase", "Manage porting phases");
|
||||
|
||||
// list
|
||||
var listCmd = new Command("list", "List all phases with status");
|
||||
listCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
using var db = new Database(dbPath);
|
||||
|
||||
Console.WriteLine($"{"Phase",-7} {"Name",-25} {"Description",-55} {"Status",-12}");
|
||||
Console.WriteLine(new string('-', 99));
|
||||
foreach (var (number, name, description) in Phases)
|
||||
{
|
||||
var status = CalculatePhaseStatus(db, number);
|
||||
Console.WriteLine($"{number,-7} {name,-25} {description,-55} {status,-12}");
|
||||
}
|
||||
});
|
||||
|
||||
// check
|
||||
var checkPhase = new Argument<int>("phase") { Description = "Phase number (1-7)" };
|
||||
var checkCmd = new Command("check", "Check phase completion status");
|
||||
checkCmd.Add(checkPhase);
|
||||
checkCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var phase = parseResult.GetValue(checkPhase);
|
||||
using var db = new Database(dbPath);
|
||||
|
||||
if (phase < 1 || phase > 7)
|
||||
{
|
||||
Console.WriteLine("Phase must be between 1 and 7.");
|
||||
return;
|
||||
}
|
||||
|
||||
var (_, name, description) = Phases[phase - 1];
|
||||
Console.WriteLine($"Phase {phase}: {name}");
|
||||
Console.WriteLine($" {description}\n");
|
||||
|
||||
RunPhaseCheck(db, phase);
|
||||
});
|
||||
|
||||
phaseCommand.Add(listCmd);
|
||||
phaseCommand.Add(checkCmd);
|
||||
|
||||
return phaseCommand;
|
||||
}
|
||||
|
||||
private static string CalculatePhaseStatus(Database db, int phase)
|
||||
{
|
||||
return phase switch
|
||||
{
|
||||
1 => CalculatePhase1Status(db),
|
||||
2 => CalculateModulePhaseStatus(db, "Core Infrastructure"),
|
||||
3 => CalculateModulePhaseStatus(db, "Message Layer"),
|
||||
4 => CalculateModulePhaseStatus(db, "Connection Layer"),
|
||||
5 => CalculateModulePhaseStatus(db, "Client API"),
|
||||
6 => CalculateModulePhaseStatus(db, "Advanced Features"),
|
||||
7 => CalculatePhase7Status(db),
|
||||
_ => "unknown"
|
||||
};
|
||||
}
|
||||
|
||||
private static string CalculatePhase1Status(Database db)
|
||||
{
|
||||
var totalModules = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules");
|
||||
var totalLibraries = db.ExecuteScalar<long>("SELECT COUNT(*) FROM library_mappings");
|
||||
if (totalModules == 0 && totalLibraries == 0) return "not_started";
|
||||
var mappedLibraries = db.ExecuteScalar<long>("SELECT COUNT(*) FROM library_mappings WHERE status != 'not_mapped'");
|
||||
if (totalModules > 0 && (totalLibraries == 0 || mappedLibraries == totalLibraries)) return "complete";
|
||||
return "in_progress";
|
||||
}
|
||||
|
||||
private static string CalculateModulePhaseStatus(Database db, string phaseDescription)
|
||||
{
|
||||
// Generic phase status based on overall module completion
|
||||
var total = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules");
|
||||
if (total == 0) return "not_started";
|
||||
var done = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules WHERE status IN ('complete', 'verified', 'n_a')");
|
||||
if (done == total) return "complete";
|
||||
var started = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules WHERE status != 'not_started'");
|
||||
return started > 0 ? "in_progress" : "not_started";
|
||||
}
|
||||
|
||||
private static string CalculatePhase7Status(Database db)
|
||||
{
|
||||
var totalTests = db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests");
|
||||
if (totalTests == 0) return "not_started";
|
||||
var doneTests = db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests WHERE status IN ('complete', 'verified', 'n_a')");
|
||||
if (doneTests == totalTests) return "complete";
|
||||
var startedTests = db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests WHERE status != 'not_started'");
|
||||
return startedTests > 0 ? "in_progress" : "not_started";
|
||||
}
|
||||
|
||||
private static void RunPhaseCheck(Database db, int phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case 1:
|
||||
CheckPhase1(db);
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
CheckModulePhase(db, phase);
|
||||
break;
|
||||
case 7:
|
||||
CheckPhase7(db);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckPhase1(Database db)
|
||||
{
|
||||
var totalModules = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules");
|
||||
var totalFeatures = db.ExecuteScalar<long>("SELECT COUNT(*) FROM features");
|
||||
var totalTests = db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests");
|
||||
var totalLibs = db.ExecuteScalar<long>("SELECT COUNT(*) FROM library_mappings");
|
||||
var mappedLibs = db.ExecuteScalar<long>("SELECT COUNT(*) FROM library_mappings WHERE status != 'not_mapped'");
|
||||
var totalDeps = db.ExecuteScalar<long>("SELECT COUNT(*) FROM dependencies");
|
||||
|
||||
Console.WriteLine("Phase 1 Checklist:");
|
||||
Console.WriteLine($" [{ (totalModules > 0 ? "x" : " ") }] Modules populated: {totalModules}");
|
||||
Console.WriteLine($" [{ (totalFeatures > 0 ? "x" : " ") }] Features populated: {totalFeatures}");
|
||||
Console.WriteLine($" [{ (totalTests > 0 ? "x" : " ") }] Unit tests populated: {totalTests}");
|
||||
Console.WriteLine($" [{ (totalDeps > 0 ? "x" : " ") }] Dependencies mapped: {totalDeps}");
|
||||
Console.WriteLine($" [{ (totalLibs > 0 ? "x" : " ") }] Libraries identified: {totalLibs}");
|
||||
Console.WriteLine($" [{ (totalLibs > 0 && mappedLibs == totalLibs ? "x" : " ") }] All libraries mapped: {mappedLibs}/{totalLibs}");
|
||||
}
|
||||
|
||||
private static void CheckModulePhase(Database db, int phase)
|
||||
{
|
||||
var totalModules = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules");
|
||||
var doneModules = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules WHERE status IN ('complete', 'verified', 'n_a')");
|
||||
var stubModules = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules WHERE status = 'stub'");
|
||||
var totalFeatures = db.ExecuteScalar<long>("SELECT COUNT(*) FROM features");
|
||||
var doneFeatures = db.ExecuteScalar<long>("SELECT COUNT(*) FROM features WHERE status IN ('complete', 'verified', 'n_a')");
|
||||
var stubFeatures = db.ExecuteScalar<long>("SELECT COUNT(*) FROM features WHERE status = 'stub'");
|
||||
|
||||
var modPct = totalModules > 0 ? (double)doneModules / totalModules * 100 : 0;
|
||||
var featPct = totalFeatures > 0 ? (double)doneFeatures / totalFeatures * 100 : 0;
|
||||
|
||||
Console.WriteLine($"Phase {phase} Progress:");
|
||||
Console.WriteLine($" Modules: {doneModules}/{totalModules} complete ({modPct:F1}%), {stubModules} stubs");
|
||||
Console.WriteLine($" Features: {doneFeatures}/{totalFeatures} complete ({featPct:F1}%), {stubFeatures} stubs");
|
||||
|
||||
// Show incomplete modules
|
||||
var incomplete = db.Query(
|
||||
"SELECT id, name, status FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a') ORDER BY name");
|
||||
if (incomplete.Count > 0)
|
||||
{
|
||||
Console.WriteLine($"\n Incomplete modules ({incomplete.Count}):");
|
||||
foreach (var m in incomplete)
|
||||
Console.WriteLine($" #{m["id"],-5} {m["name"],-30} {m["status"]}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckPhase7(Database db)
|
||||
{
|
||||
var totalTests = db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests");
|
||||
var doneTests = db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests WHERE status IN ('complete', 'verified', 'n_a')");
|
||||
var stubTests = db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests WHERE status = 'stub'");
|
||||
var verifiedTests = db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests WHERE status = 'verified'");
|
||||
|
||||
var pct = totalTests > 0 ? (double)doneTests / totalTests * 100 : 0;
|
||||
|
||||
Console.WriteLine("Phase 7 Progress:");
|
||||
Console.WriteLine($" Total tests: {totalTests}");
|
||||
Console.WriteLine($" Complete: {doneTests - verifiedTests}");
|
||||
Console.WriteLine($" Verified: {verifiedTests}");
|
||||
Console.WriteLine($" Stubs: {stubTests}");
|
||||
Console.WriteLine($" Not started: {totalTests - doneTests - stubTests}");
|
||||
Console.WriteLine($" Progress: {pct:F1}%");
|
||||
|
||||
// Modules with incomplete tests
|
||||
var modulesWithIncomplete = db.Query(@"
|
||||
SELECT m.id, m.name, COUNT(*) as total,
|
||||
SUM(CASE WHEN t.status IN ('complete', 'verified', 'n_a') THEN 1 ELSE 0 END) as done
|
||||
FROM unit_tests t
|
||||
JOIN modules m ON t.module_id = m.id
|
||||
GROUP BY m.id, m.name
|
||||
HAVING done < total
|
||||
ORDER BY m.name");
|
||||
if (modulesWithIncomplete.Count > 0)
|
||||
{
|
||||
Console.WriteLine($"\n Modules with incomplete tests ({modulesWithIncomplete.Count}):");
|
||||
foreach (var m in modulesWithIncomplete)
|
||||
Console.WriteLine($" #{m["id"],-5} {m["name"],-30} {m["done"]}/{m["total"]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
58
tools/NatsNet.PortTracker/Commands/ReportCommands.cs
Normal file
58
tools/NatsNet.PortTracker/Commands/ReportCommands.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.CommandLine;
|
||||
using NatsNet.PortTracker.Data;
|
||||
using NatsNet.PortTracker.Reporting;
|
||||
|
||||
namespace NatsNet.PortTracker.Commands;
|
||||
|
||||
public static class ReportCommands
|
||||
{
|
||||
public static Command Create(Option<string> dbOption, Option<string> schemaOption)
|
||||
{
|
||||
var reportCommand = new Command("report", "Generate reports");
|
||||
|
||||
// summary
|
||||
var summaryCmd = new Command("summary", "Show status summary");
|
||||
summaryCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
using var db = new Database(dbPath);
|
||||
ReportGenerator.PrintSummary(db);
|
||||
});
|
||||
|
||||
// export
|
||||
var exportFormat = new Option<string>("--format") { Description = "Export format (md)", DefaultValueFactory = _ => "md" };
|
||||
var exportOutput = new Option<string?>("--output") { Description = "Output file path (stdout if not specified)" };
|
||||
var exportCmd = new Command("export", "Export status report");
|
||||
exportCmd.Add(exportFormat);
|
||||
exportCmd.Add(exportOutput);
|
||||
exportCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var format = parseResult.GetValue(exportFormat)!;
|
||||
var output = parseResult.GetValue(exportOutput);
|
||||
using var db = new Database(dbPath);
|
||||
|
||||
if (format != "md")
|
||||
{
|
||||
Console.WriteLine($"Unsupported format: {format}. Supported: md");
|
||||
return;
|
||||
}
|
||||
|
||||
var markdown = ReportGenerator.ExportMarkdown(db);
|
||||
if (output is not null)
|
||||
{
|
||||
File.WriteAllText(output, markdown);
|
||||
Console.WriteLine($"Report exported to {output}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(markdown);
|
||||
}
|
||||
});
|
||||
|
||||
reportCommand.Add(summaryCmd);
|
||||
reportCommand.Add(exportCmd);
|
||||
|
||||
return reportCommand;
|
||||
}
|
||||
}
|
||||
143
tools/NatsNet.PortTracker/Commands/TestCommands.cs
Normal file
143
tools/NatsNet.PortTracker/Commands/TestCommands.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System.CommandLine;
|
||||
using NatsNet.PortTracker.Data;
|
||||
|
||||
namespace NatsNet.PortTracker.Commands;
|
||||
|
||||
public static class TestCommands
|
||||
{
|
||||
public static Command Create(Option<string> dbOption, Option<string> schemaOption)
|
||||
{
|
||||
var testCommand = new Command("test", "Manage unit tests");
|
||||
|
||||
// list
|
||||
var listModule = new Option<int?>("--module") { Description = "Filter by module ID" };
|
||||
var listStatus = new Option<string?>("--status") { Description = "Filter by status" };
|
||||
var listCmd = new Command("list", "List unit tests");
|
||||
listCmd.Add(listModule);
|
||||
listCmd.Add(listStatus);
|
||||
listCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var moduleId = parseResult.GetValue(listModule);
|
||||
var status = parseResult.GetValue(listStatus);
|
||||
using var db = new Database(dbPath);
|
||||
var sql = "SELECT t.id, t.name, t.status, t.module_id, m.name as module_name, t.go_method, t.dotnet_method FROM unit_tests t LEFT JOIN modules m ON t.module_id = m.id";
|
||||
var parameters = new List<(string, object?)>();
|
||||
var clauses = new List<string>();
|
||||
if (moduleId is not null)
|
||||
{
|
||||
clauses.Add("t.module_id = @module");
|
||||
parameters.Add(("@module", moduleId));
|
||||
}
|
||||
if (status is not null)
|
||||
{
|
||||
clauses.Add("t.status = @status");
|
||||
parameters.Add(("@status", status));
|
||||
}
|
||||
if (clauses.Count > 0)
|
||||
sql += " WHERE " + string.Join(" AND ", clauses);
|
||||
sql += " ORDER BY m.name, t.name";
|
||||
|
||||
var rows = db.Query(sql, parameters.ToArray());
|
||||
Console.WriteLine($"{"ID",-5} {"Name",-30} {"Status",-15} {"Module",-20} {"Go Method",-25} {"DotNet Method",-25}");
|
||||
Console.WriteLine(new string('-', 120));
|
||||
foreach (var row in rows)
|
||||
{
|
||||
Console.WriteLine($"{row["id"],-5} {Truncate(row["name"]?.ToString(), 29),-30} {row["status"],-15} {Truncate(row["module_name"]?.ToString(), 19),-20} {Truncate(row["go_method"]?.ToString(), 24),-25} {Truncate(row["dotnet_method"]?.ToString(), 24),-25}");
|
||||
}
|
||||
Console.WriteLine($"\nTotal: {rows.Count} tests");
|
||||
});
|
||||
|
||||
// show
|
||||
var showId = new Argument<int>("id") { Description = "Test ID" };
|
||||
var showCmd = new Command("show", "Show test details");
|
||||
showCmd.Add(showId);
|
||||
showCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var id = parseResult.GetValue(showId);
|
||||
using var db = new Database(dbPath);
|
||||
var tests = db.Query(
|
||||
"SELECT t.*, m.name as module_name FROM unit_tests t LEFT JOIN modules m ON t.module_id = m.id WHERE t.id = @id",
|
||||
("@id", id));
|
||||
if (tests.Count == 0)
|
||||
{
|
||||
Console.WriteLine($"Test {id} not found.");
|
||||
return;
|
||||
}
|
||||
var t = tests[0];
|
||||
Console.WriteLine($"Test #{t["id"]}: {t["name"]}");
|
||||
Console.WriteLine($" Module: #{t["module_id"]} ({t["module_name"]})");
|
||||
Console.WriteLine($" Feature: {(t["feature_id"] is not null ? $"#{t["feature_id"]}" : "(none)")}");
|
||||
Console.WriteLine($" Status: {t["status"]}");
|
||||
Console.WriteLine($" Go File: {t["go_file"]}");
|
||||
Console.WriteLine($" Go Class: {t["go_class"]}");
|
||||
Console.WriteLine($" Go Method: {t["go_method"]}");
|
||||
Console.WriteLine($" Go Line: {t["go_line_number"]}");
|
||||
Console.WriteLine($" Go LOC: {t["go_line_count"]}");
|
||||
Console.WriteLine($" .NET: {t["dotnet_project"]} / {t["dotnet_class"]} / {t["dotnet_method"]}");
|
||||
Console.WriteLine($" Notes: {t["notes"]}");
|
||||
|
||||
var deps = db.Query(
|
||||
"SELECT d.target_type, d.target_id, d.dependency_kind FROM dependencies d WHERE d.source_type = 'unit_test' 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["dependency_kind"]}]");
|
||||
});
|
||||
|
||||
// update
|
||||
var updateId = new Argument<int>("id") { Description = "Test ID" };
|
||||
var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
|
||||
var updateCmd = new Command("update", "Update test 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 unit_tests SET status = @status WHERE id = @id",
|
||||
("@status", status), ("@id", id));
|
||||
Console.WriteLine(affected > 0 ? $"Test {id} updated to '{status}'." : $"Test {id} not found.");
|
||||
});
|
||||
|
||||
// map
|
||||
var mapId = new Argument<int>("id") { Description = "Test ID" };
|
||||
var mapProject = new Option<string?>("--project") { Description = "Target .NET project" };
|
||||
var mapClass = new Option<string?>("--class") { Description = "Target .NET test class" };
|
||||
var mapMethod = new Option<string?>("--method") { Description = "Target .NET test method" };
|
||||
var mapCmd = new Command("map", "Map test to .NET test method");
|
||||
mapCmd.Add(mapId);
|
||||
mapCmd.Add(mapProject);
|
||||
mapCmd.Add(mapClass);
|
||||
mapCmd.Add(mapMethod);
|
||||
mapCmd.SetAction(parseResult =>
|
||||
{
|
||||
var dbPath = parseResult.GetValue(dbOption)!;
|
||||
var id = parseResult.GetValue(mapId);
|
||||
var project = parseResult.GetValue(mapProject);
|
||||
var cls = parseResult.GetValue(mapClass);
|
||||
var method = parseResult.GetValue(mapMethod);
|
||||
using var db = new Database(dbPath);
|
||||
var affected = db.Execute(
|
||||
"UPDATE unit_tests SET dotnet_project = COALESCE(@project, dotnet_project), dotnet_class = COALESCE(@cls, dotnet_class), dotnet_method = COALESCE(@method, dotnet_method) WHERE id = @id",
|
||||
("@project", project), ("@cls", cls), ("@method", method), ("@id", id));
|
||||
Console.WriteLine(affected > 0 ? $"Test {id} mapped." : $"Test {id} not found.");
|
||||
});
|
||||
|
||||
testCommand.Add(listCmd);
|
||||
testCommand.Add(showCmd);
|
||||
testCommand.Add(updateCmd);
|
||||
testCommand.Add(mapCmd);
|
||||
|
||||
return testCommand;
|
||||
}
|
||||
|
||||
private static string Truncate(string? s, int maxLen)
|
||||
{
|
||||
if (s is null) return "";
|
||||
return s.Length <= maxLen ? s : s[..(maxLen - 2)] + "..";
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,12 @@ initCommand.SetAction(parseResult =>
|
||||
|
||||
rootCommand.Add(initCommand);
|
||||
rootCommand.Add(ModuleCommands.Create(dbOption, schemaOption));
|
||||
rootCommand.Add(FeatureCommands.Create(dbOption, schemaOption));
|
||||
rootCommand.Add(TestCommands.Create(dbOption, schemaOption));
|
||||
rootCommand.Add(LibraryCommands.Create(dbOption, schemaOption));
|
||||
rootCommand.Add(DependencyCommands.Create(dbOption, schemaOption));
|
||||
rootCommand.Add(ReportCommands.Create(dbOption, schemaOption));
|
||||
rootCommand.Add(PhaseCommands.Create(dbOption, schemaOption));
|
||||
|
||||
var parseResult = rootCommand.Parse(args);
|
||||
return await parseResult.InvokeAsync();
|
||||
|
||||
95
tools/NatsNet.PortTracker/Reporting/ReportGenerator.cs
Normal file
95
tools/NatsNet.PortTracker/Reporting/ReportGenerator.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using NatsNet.PortTracker.Data;
|
||||
|
||||
namespace NatsNet.PortTracker.Reporting;
|
||||
|
||||
public static class ReportGenerator
|
||||
{
|
||||
public static void PrintSummary(Database db)
|
||||
{
|
||||
Console.WriteLine("=== Porting Status Summary ===\n");
|
||||
|
||||
PrintTableSummary(db, "modules", "Modules");
|
||||
PrintTableSummary(db, "features", "Features");
|
||||
PrintTableSummary(db, "unit_tests", "Unit Tests");
|
||||
PrintLibrarySummary(db);
|
||||
|
||||
// Overall progress
|
||||
var totalItems = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules") +
|
||||
db.ExecuteScalar<long>("SELECT COUNT(*) FROM features") +
|
||||
db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests");
|
||||
var doneItems = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules WHERE status IN ('complete', 'verified', 'n_a')") +
|
||||
db.ExecuteScalar<long>("SELECT COUNT(*) FROM features WHERE status IN ('complete', 'verified', 'n_a')") +
|
||||
db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests WHERE status IN ('complete', 'verified', 'n_a')");
|
||||
var pct = totalItems > 0 ? (double)doneItems / totalItems * 100 : 0;
|
||||
Console.WriteLine($"\nOverall Progress: {doneItems}/{totalItems} ({pct:F1}%)");
|
||||
}
|
||||
|
||||
public static string ExportMarkdown(Database db)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("# NATS .NET Porting Status Report");
|
||||
sb.AppendLine($"\nGenerated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC\n");
|
||||
|
||||
AppendTableMarkdown(sb, db, "modules", "Modules");
|
||||
AppendTableMarkdown(sb, db, "features", "Features");
|
||||
AppendTableMarkdown(sb, db, "unit_tests", "Unit Tests");
|
||||
AppendLibraryMarkdown(sb, db);
|
||||
|
||||
// Overall
|
||||
var totalItems = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules") +
|
||||
db.ExecuteScalar<long>("SELECT COUNT(*) FROM features") +
|
||||
db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests");
|
||||
var doneItems = db.ExecuteScalar<long>("SELECT COUNT(*) FROM modules WHERE status IN ('complete', 'verified', 'n_a')") +
|
||||
db.ExecuteScalar<long>("SELECT COUNT(*) FROM features WHERE status IN ('complete', 'verified', 'n_a')") +
|
||||
db.ExecuteScalar<long>("SELECT COUNT(*) FROM unit_tests WHERE status IN ('complete', 'verified', 'n_a')");
|
||||
var pct = totalItems > 0 ? (double)doneItems / totalItems * 100 : 0;
|
||||
sb.AppendLine($"\n## Overall Progress\n");
|
||||
sb.AppendLine($"**{doneItems}/{totalItems} items complete ({pct:F1}%)**");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void PrintTableSummary(Database db, string table, string label)
|
||||
{
|
||||
var rows = db.Query($"SELECT status, COUNT(*) as cnt FROM {table} GROUP BY status ORDER BY status");
|
||||
var total = rows.Sum(r => Convert.ToInt64(r["cnt"]));
|
||||
Console.WriteLine($"{label} ({total} total):");
|
||||
foreach (var row in rows)
|
||||
Console.WriteLine($" {row["status"],-15} {row["cnt"],5}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static void PrintLibrarySummary(Database db)
|
||||
{
|
||||
var rows = db.Query("SELECT status, COUNT(*) as cnt FROM library_mappings GROUP BY status ORDER BY status");
|
||||
var total = rows.Sum(r => Convert.ToInt64(r["cnt"]));
|
||||
Console.WriteLine($"Library Mappings ({total} total):");
|
||||
foreach (var row in rows)
|
||||
Console.WriteLine($" {row["status"],-15} {row["cnt"],5}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static void AppendTableMarkdown(System.Text.StringBuilder sb, Database db, string table, string label)
|
||||
{
|
||||
var rows = db.Query($"SELECT status, COUNT(*) as cnt FROM {table} GROUP BY status ORDER BY status");
|
||||
var total = rows.Sum(r => Convert.ToInt64(r["cnt"]));
|
||||
sb.AppendLine($"## {label} ({total} total)\n");
|
||||
sb.AppendLine("| Status | Count |");
|
||||
sb.AppendLine("|--------|-------|");
|
||||
foreach (var row in rows)
|
||||
sb.AppendLine($"| {row["status"]} | {row["cnt"]} |");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
private static void AppendLibraryMarkdown(System.Text.StringBuilder sb, Database db)
|
||||
{
|
||||
var rows = db.Query("SELECT status, COUNT(*) as cnt FROM library_mappings GROUP BY status ORDER BY status");
|
||||
var total = rows.Sum(r => Convert.ToInt64(r["cnt"]));
|
||||
sb.AppendLine($"## Library Mappings ({total} total)\n");
|
||||
sb.AppendLine("| Status | Count |");
|
||||
sb.AppendLine("|--------|-------|");
|
||||
foreach (var row in rows)
|
||||
sb.AppendLine($"| {row["status"]} | {row["cnt"]} |");
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user