namespace StellaOps.Concelier.Merge.Comparers; using System; using StellaOps.Concelier.Normalization.Distro; public sealed class NevraComparer : IComparer, IComparer { public static NevraComparer Instance { get; } = new(); private NevraComparer() { } public int Compare(string? x, string? y) { if (ReferenceEquals(x, y)) { return 0; } if (x is null) { return -1; } if (y is null) { return 1; } var xParsed = Nevra.TryParse(x, out var xNevra); var yParsed = Nevra.TryParse(y, out var yNevra); if (xParsed && yParsed) { return Compare(xNevra, yNevra); } if (xParsed) { return 1; } if (yParsed) { return -1; } return string.Compare(x, y, StringComparison.Ordinal); } public int Compare(Nevra? x, Nevra? y) { if (ReferenceEquals(x, y)) { return 0; } if (x is null) { return -1; } if (y is null) { return 1; } var compare = string.Compare(x.Name, y.Name, StringComparison.Ordinal); if (compare != 0) { return compare; } compare = string.Compare(x.Architecture ?? string.Empty, y.Architecture ?? string.Empty, StringComparison.Ordinal); if (compare != 0) { return compare; } compare = x.Epoch.CompareTo(y.Epoch); if (compare != 0) { return compare; } compare = RpmVersionComparer.Compare(x.Version, y.Version); if (compare != 0) { return compare; } compare = RpmVersionComparer.Compare(x.Release, y.Release); if (compare != 0) { return compare; } return string.Compare(x.Original, y.Original, StringComparison.Ordinal); } } internal static class RpmVersionComparer { public static int Compare(string? left, string? right) { left ??= string.Empty; right ??= string.Empty; var i = 0; var j = 0; while (true) { var leftHasTilde = SkipToNextSegment(left, ref i); var rightHasTilde = SkipToNextSegment(right, ref j); if (leftHasTilde || rightHasTilde) { if (leftHasTilde && rightHasTilde) { continue; } return leftHasTilde ? -1 : 1; } var leftEnd = i >= left.Length; var rightEnd = j >= right.Length; if (leftEnd || rightEnd) { if (leftEnd && rightEnd) { return 0; } return leftEnd ? -1 : 1; } var leftDigit = char.IsDigit(left[i]); var rightDigit = char.IsDigit(right[j]); if (leftDigit && !rightDigit) { return 1; } if (!leftDigit && rightDigit) { return -1; } int compare; if (leftDigit) { compare = CompareNumericSegment(left, ref i, right, ref j); } else { compare = CompareAlphaSegment(left, ref i, right, ref j); } if (compare != 0) { return compare; } } } private static bool SkipToNextSegment(string value, ref int index) { var sawTilde = false; while (index < value.Length) { var current = value[index]; if (current == '~') { sawTilde = true; index++; break; } if (char.IsLetterOrDigit(current)) { break; } index++; } return sawTilde; } private static int CompareNumericSegment(string value, ref int index, string other, ref int otherIndex) { var start = index; while (index < value.Length && char.IsDigit(value[index])) { index++; } var otherStart = otherIndex; while (otherIndex < other.Length && char.IsDigit(other[otherIndex])) { otherIndex++; } var trimmedStart = start; while (trimmedStart < index && value[trimmedStart] == '0') { trimmedStart++; } var otherTrimmedStart = otherStart; while (otherTrimmedStart < otherIndex && other[otherTrimmedStart] == '0') { otherTrimmedStart++; } var length = index - trimmedStart; var otherLength = otherIndex - otherTrimmedStart; if (length != otherLength) { return length.CompareTo(otherLength); } var comparison = value.AsSpan(trimmedStart, length) .CompareTo(other.AsSpan(otherTrimmedStart, otherLength), StringComparison.Ordinal); if (comparison != 0) { return comparison; } return 0; } private static int CompareAlphaSegment(string value, ref int index, string other, ref int otherIndex) { var start = index; while (index < value.Length && char.IsLetter(value[index])) { index++; } var otherStart = otherIndex; while (otherIndex < other.Length && char.IsLetter(other[otherIndex])) { otherIndex++; } var length = index - start; var otherLength = otherIndex - otherStart; var comparison = value.AsSpan(start, length) .CompareTo(other.AsSpan(otherStart, otherLength), StringComparison.Ordinal); if (comparison != 0) { return comparison; } return 0; } }