Files
natsnet/tools/NatsNet.PortTracker/Commands/DependencyCommands.cs

147 lines
6.6 KiB
C#

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)] + "..";
}
}