| 1 | using AgentForge.Abstractions; |
| 2 | using AgentForge.Plugin.Grouping.Core; |
| 3 | |
| 4 | namespace AgentForge.Plugin.Grouping.Releases.Axes; |
| 5 | |
| 6 | internal abstract class ForgeFixedSectionReleaseGroupingAxis( |
| 7 | string id, |
| 8 | string label, |
| 9 | int order, |
| 10 | IReadOnlyList<ForgeGroupingSection> sections) |
| 11 | : ForgeFixedSectionGroupingAxis<ForgeReleaseListItem>(id, label, order, sections), |
| 12 | IForgeReleaseGroupingAxis; |
| 13 | |
| 14 | internal sealed class ReleaseChannelGroupingAxis() |
| 15 | : ForgeFixedSectionReleaseGroupingAxis( |
| 16 | "channel", |
| 17 | "Channel", |
| 18 | order: 10, |
| 19 | sections: |
| 20 | [ |
| 21 | new("stable", "Stable"), |
| 22 | new("prerelease", "Pre-release"), |
| 23 | new("draft", "Draft"), |
| 24 | ]) |
| 25 | { |
| 26 | public override string GetKey(ForgeReleaseListItem item, DateTimeOffset utcNow) => |
| 27 | item.IsPrerelease ? "prerelease" : "stable"; |
| 28 | } |
| 29 | |
| 30 | internal sealed class ReleaseMajorVersionGroupingAxis() : IForgeReleaseGroupingAxis |
| 31 | { |
| 32 | public string Id => "major"; |
| 33 | |
| 34 | public string Label => "Major version"; |
| 35 | |
| 36 | public int Order => 20; |
| 37 | |
| 38 | public string GetKey(ForgeReleaseListItem item, DateTimeOffset utcNow) => |
| 39 | ParseMajor(item.Tag); |
| 40 | |
| 41 | public IReadOnlyList<ForgeGroupingSection> SectionsFor( |
| 42 | IReadOnlyList<ForgeReleaseListItem> items, |
| 43 | DateTimeOffset utcNow) |
| 44 | { |
| 45 | var keys = items |
| 46 | .Select(item => ParseMajor(item.Tag)) |
| 47 | .Distinct(StringComparer.OrdinalIgnoreCase) |
| 48 | .OrderByDescending(key => int.TryParse(key, out var n) ? n : int.MinValue) |
| 49 | .ThenBy(key => key, StringComparer.OrdinalIgnoreCase) |
| 50 | .Select(key => new ForgeGroupingSection(key, key == "unknown" ? "Unknown" : $"v{key}")) |
| 51 | .ToList(); |
| 52 | |
| 53 | return keys.Count == 0 ? [new("unknown", "Unknown")] : keys; |
| 54 | } |
| 55 | |
| 56 | private static string ParseMajor(string tag) |
| 57 | { |
| 58 | var trimmed = tag.Trim(); |
| 59 | if (trimmed.StartsWith('v') || trimmed.StartsWith('V')) |
| 60 | trimmed = trimmed[1..]; |
| 61 | |
| 62 | var dot = trimmed.IndexOf('.'); |
| 63 | var majorPart = dot >= 0 ? trimmed[..dot] : trimmed; |
| 64 | return int.TryParse(majorPart, out _) ? majorPart : "unknown"; |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | internal sealed class ReleasePublishedGroupingAxis() |
| 69 | : ForgeFixedSectionReleaseGroupingAxis( |
| 70 | "published", |
| 71 | "Published", |
| 72 | order: 30, |
| 73 | ForgeGroupingDateBuckets.Sections()) |
| 74 | { |
| 75 | public override string GetKey(ForgeReleaseListItem item, DateTimeOffset utcNow) => |
| 76 | ForgeGroupingDateBuckets.Bucket(item.PublishedAt, utcNow); |
| 77 | } |
| 78 | |
| 79 | internal sealed class ReleaseTargetPlatformGroupingAxis() |
| 80 | : ForgeFixedSectionReleaseGroupingAxis( |
| 81 | "target_platform", |
| 82 | "Target platform", |
| 83 | order: 40, |
| 84 | sections: |
| 85 | [ |
| 86 | new("windows", "Windows"), |
| 87 | new("linux", "Linux"), |
| 88 | new("macos", "macOS"), |
| 89 | new("multi", "Multi-platform"), |
| 90 | new("unknown", "Unknown"), |
| 91 | ]) |
| 92 | { |
| 93 | public override string GetKey(ForgeReleaseListItem item, DateTimeOffset utcNow) => |
| 94 | ReleaseGroupingAxisHelpers.NormalizePlatform(item.TargetPlatform); |
| 95 | } |
| 96 | |
| 97 | internal sealed class ReleasePackageFormatGroupingAxis() |
| 98 | : ForgeFixedSectionReleaseGroupingAxis( |
| 99 | "package_format", |
| 100 | "Package format", |
| 101 | order: 50, |
| 102 | sections: |
| 103 | [ |
| 104 | new("exe", "EXE"), |
| 105 | new("msi", "MSI"), |
| 106 | new("zip", "ZIP"), |
| 107 | new("nupkg", "NuGet"), |
| 108 | new("na", "N/A"), |
| 109 | new("unknown", "Unknown"), |
| 110 | ]) |
| 111 | { |
| 112 | public override string GetKey(ForgeReleaseListItem item, DateTimeOffset utcNow) |
| 113 | { |
| 114 | if (!string.Equals(ReleaseGroupingAxisHelpers.NormalizePlatform(item.TargetPlatform), "windows", StringComparison.OrdinalIgnoreCase)) |
| 115 | return "na"; |
| 116 | |
| 117 | return ReleaseGroupingAxisHelpers.NormalizeFormat(item.PackageFormat); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | internal static class ReleaseGroupingAxisHelpers |
| 122 | { |
| 123 | internal static string NormalizePlatform(string? platform) |
| 124 | { |
| 125 | if (string.IsNullOrWhiteSpace(platform)) |
| 126 | return "unknown"; |
| 127 | |
| 128 | return platform.Trim().ToLowerInvariant() switch |
| 129 | { |
| 130 | "win" or "windows" or "win-x64" or "win-x86" => "windows", |
| 131 | "linux" => "linux", |
| 132 | "osx" or "macos" or "darwin" => "macos", |
| 133 | "multi" or "multi-platform" or "all" => "multi", |
| 134 | _ => platform.Trim().ToLowerInvariant(), |
| 135 | }; |
| 136 | } |
| 137 | |
| 138 | internal static string NormalizeFormat(string? format) |
| 139 | { |
| 140 | if (string.IsNullOrWhiteSpace(format)) |
| 141 | return "unknown"; |
| 142 | |
| 143 | return format.Trim().ToLowerInvariant() switch |
| 144 | { |
| 145 | "exe" or "winexe" => "exe", |
| 146 | "msi" or "wix" => "msi", |
| 147 | "zip" or "archive" => "zip", |
| 148 | "nupkg" or "nuget" => "nupkg", |
| 149 | _ => format.Trim().ToLowerInvariant(), |
| 150 | }; |
| 151 | } |
| 152 | } |
| 153 | |