184 lines
8.8 KiB
C#
184 lines
8.8 KiB
C#
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)] + "..";
|
|
}
|
|
}
|