forked from BilalY/Rasagar
148 lines
5.5 KiB
C#
148 lines
5.5 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace PvpXray
|
|
{
|
|
/// A SemVer version triple, with no pre-release tag or build information.
|
|
public struct VersionTriple
|
|
{
|
|
public static readonly VersionTriple MaxValue = new VersionTriple(uint.MaxValue, uint.MaxValue, uint.MaxValue);
|
|
|
|
public uint Major;
|
|
public uint Minor;
|
|
public uint Patch;
|
|
|
|
public VersionTriple NextPatch => new VersionTriple(Major, Minor, Patch + 1);
|
|
public VersionTriple NextMinor => new VersionTriple(Major, Minor + 1, 0);
|
|
|
|
public VersionTriple(uint major, uint minor, uint patch)
|
|
{
|
|
Major = major;
|
|
Minor = minor;
|
|
Patch = patch;
|
|
}
|
|
|
|
public int CompareTo(VersionTriple other)
|
|
{
|
|
if (Major < other.Major) return -1;
|
|
if (Major > other.Major) return +1;
|
|
if (Minor < other.Minor) return -1;
|
|
if (Minor > other.Minor) return +1;
|
|
if (Patch < other.Patch) return -1;
|
|
if (Patch > other.Patch) return +1;
|
|
return 0;
|
|
}
|
|
|
|
public static bool TryParse(string version, out VersionTriple triple)
|
|
=> TryParse(version, version.Length, out triple);
|
|
|
|
static readonly char[] k_Separators = { '-', '+' };
|
|
|
|
/// Parses the MAJOR.MINOR.PATCH part of a SemVer string, ignoring any
|
|
/// pre-release tag or build information. Note that only the version
|
|
/// triple is validated to conform with SemVer, not the ignored part.
|
|
public static bool TryParseIgnoringPrereleaseAndBuildInfo(string version, out VersionTriple triple)
|
|
{
|
|
var i = version.IndexOfAny(k_Separators);
|
|
return TryParse(version, i == -1 ? version.Length : i, out triple);
|
|
}
|
|
|
|
static bool TryParse(string version, int len, out VersionTriple triple)
|
|
{
|
|
triple = default;
|
|
|
|
var i = version.IndexOf('.');
|
|
var j = version.IndexOf('.', i + 1);
|
|
if (j == -1 || j >= len - 1) return false;
|
|
if ((i != 1 && version[0] == '0') ||
|
|
(j != i + 2 && version[i + 1] == '0') ||
|
|
(len != j + 2 && version[j + 1] == '0')) return false;
|
|
|
|
var majorText = version.SpanOrSubstring(0, i);
|
|
var minorText = version.SpanOrSubstring(i + 1, j - i - 1);
|
|
var patchText = version.SpanOrSubstring(j + 1, len - j - 1);
|
|
return uint.TryParse(majorText, NumberStyles.None, CultureInfo.InvariantCulture, out triple.Major) &&
|
|
uint.TryParse(minorText, NumberStyles.None, CultureInfo.InvariantCulture, out triple.Minor) &&
|
|
uint.TryParse(patchText, NumberStyles.None, CultureInfo.InvariantCulture, out triple.Patch);
|
|
}
|
|
|
|
public void AppendTo(StringBuilder sb)
|
|
{
|
|
sb.Append(Major);
|
|
sb.Append('.');
|
|
sb.Append(Minor);
|
|
sb.Append('.');
|
|
sb.Append(Patch);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
var sb = new StringBuilder();
|
|
AppendTo(sb);
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
public struct SemVerQuery
|
|
{
|
|
public enum Op
|
|
{
|
|
LessThan = 0,
|
|
LessThanOrEqual = 1,
|
|
GreaterThan = 2,
|
|
GreaterThanOrEqual = 3,
|
|
}
|
|
|
|
public static readonly string[] OperatorNames = { " < ", " <= ", " > ", " >= " };
|
|
|
|
public readonly Op Operator;
|
|
public readonly VersionTriple RefVersion;
|
|
|
|
public string BestVersion;
|
|
public VersionTriple BestTriple;
|
|
|
|
public SemVerQuery(Op op, VersionTriple refVersion)
|
|
{
|
|
Operator = op;
|
|
RefVersion = refVersion;
|
|
BestVersion = null;
|
|
BestTriple = op == Op.LessThan || op == Op.LessThanOrEqual ? default : VersionTriple.MaxValue;
|
|
}
|
|
|
|
public void Consider(string candidateVersion)
|
|
{
|
|
// SemVer queries are used for compatibility checks, and thus ignore
|
|
// pre-release versions (including all 0.* versions), versions with
|
|
// build information, and versions that are not valid SemVer. For
|
|
// practical reasons we also ignore valid SemVer with ridiculously
|
|
// high component values, like "1.0.4294967296".
|
|
if (!VersionTriple.TryParse(candidateVersion, out var candidateTriple) || candidateTriple.Major == 0) return;
|
|
|
|
var direction = Direction(Operator);
|
|
|
|
var cmp = candidateTriple.CompareTo(RefVersion);
|
|
if (cmp == direction)
|
|
{
|
|
cmp = candidateTriple.CompareTo(BestTriple);
|
|
if (cmp != direction) // at least as good as current best
|
|
{
|
|
// We need to check "at least as good" and not just "better" for the
|
|
// edge case considering VersionTriple.MaxValue with Op ">" or ">=",
|
|
// since in that case, BestTriple is already MaxValue, but we must
|
|
// still assign BestVersion to signify that we found a match.
|
|
BestVersion = candidateVersion;
|
|
BestTriple = candidateTriple;
|
|
}
|
|
}
|
|
else if (cmp == 0 && (Operator == Op.LessThanOrEqual || Operator == Op.GreaterThanOrEqual))
|
|
{
|
|
BestVersion = candidateVersion;
|
|
BestTriple = candidateTriple;
|
|
}
|
|
}
|
|
|
|
static int Direction(Op op) => op == Op.LessThan || op == Op.LessThanOrEqual ? -1 : 1;
|
|
}
|
|
}
|