#nullable enable using System; namespace ShrinkSDK.Installer { public enum ShrinkPackageVersionRelation { Unknown, Older, Equal, Newer } public static class ShrinkPackageVersion { public static ShrinkPackageVersionRelation Classify(string? installedVersion, string? targetVersion) { return TryCompare(installedVersion, targetVersion, out var comparison) ? comparison < 0 ? ShrinkPackageVersionRelation.Older : comparison > 0 ? ShrinkPackageVersionRelation.Newer : ShrinkPackageVersionRelation.Equal : ShrinkPackageVersionRelation.Unknown; } public static bool TryCompare(string? left, string? right, out int comparison) { comparison = 0; if (!TryParse(left, out var leftVersion) || !TryParse(right, out var rightVersion)) return false; for (var index = 0; index < 3; index++) { comparison = leftVersion.Core[index].CompareTo(rightVersion.Core[index]); if (comparison != 0) return true; } comparison = ComparePrerelease(leftVersion.Prerelease, rightVersion.Prerelease); return true; } private static bool TryParse(string? raw, out ParsedVersion version) { version = default; if (string.IsNullOrWhiteSpace(raw)) return false; var value = raw.Trim(); if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) value = value.Substring(1); var buildIndex = value.IndexOf('+'); if (buildIndex >= 0) value = value.Substring(0, buildIndex); var prereleaseIndex = value.IndexOf('-'); var coreText = prereleaseIndex >= 0 ? value.Substring(0, prereleaseIndex) : value; var prerelease = prereleaseIndex >= 0 ? value.Substring(prereleaseIndex + 1) : string.Empty; var coreParts = coreText.Split('.'); if (coreParts.Length is < 1 or > 3) return false; var core = new int[3]; for (var index = 0; index < coreParts.Length; index++) { if (!int.TryParse(coreParts[index], out core[index]) || core[index] < 0) return false; } if (prereleaseIndex >= 0 && string.IsNullOrWhiteSpace(prerelease)) return false; version = new ParsedVersion(core, prerelease); return true; } private static int ComparePrerelease(string left, string right) { var leftEmpty = string.IsNullOrEmpty(left); var rightEmpty = string.IsNullOrEmpty(right); if (leftEmpty || rightEmpty) return leftEmpty == rightEmpty ? 0 : leftEmpty ? 1 : -1; var leftParts = left.Split('.'); var rightParts = right.Split('.'); var count = Math.Min(leftParts.Length, rightParts.Length); for (var index = 0; index < count; index++) { var leftNumeric = int.TryParse(leftParts[index], out var leftValue); var rightNumeric = int.TryParse(rightParts[index], out var rightValue); int comparison; if (leftNumeric && rightNumeric) comparison = leftValue.CompareTo(rightValue); else if (leftNumeric != rightNumeric) comparison = leftNumeric ? -1 : 1; else comparison = string.Compare(leftParts[index], rightParts[index], StringComparison.Ordinal); if (comparison != 0) return comparison; } return leftParts.Length.CompareTo(rightParts.Length); } private readonly struct ParsedVersion { public ParsedVersion(int[] core, string prerelease) { Core = core; Prerelease = prerelease; } public int[] Core { get; } public string Prerelease { get; } } } }