// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Globalization; using System.Text; using System.Text.RegularExpressions; /// /// Class for representing semantic versions. /// /// /// Original from Cake build tool source: /// https://github.com/cake-build/cake/blob/9828d7b246d332054896e52ba56983822feb3f05/src/Cake.Common/SemanticVersion.cs /// public class SemVersion : IComparable, IComparable, IEquatable { /// /// Gets the default version of a SemanticVersion. /// public static SemVersion Zero { get; } = new SemVersion(0, 0, 0, null, null, "0.0.0"); /// /// Regex property for parsing a semantic version number. /// public static readonly Regex SemVerRegex = new Regex( @"(?0|(?:[1-9]\d*))(?:\.(?0|(?:[1-9]\d*))(?:\.(?0|(?:[1-9]\d*)))?(?:\-(?[0-9A-Z\.-]+))?(?:\+(?[0-9A-Z\.-]+))?)?", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); /// /// Gets the major number of the version. /// public int Major { get; } /// /// Gets the minor number of the version. /// public int Minor { get; } /// /// Gets the patch number of the version. /// public int Patch { get; } /// /// Gets the prerelease of the version. /// public string PreRelease { get; } /// /// Gets the meta of the version. /// public string Meta { get; } /// /// Gets a value indicating whether semantic version is a prerelease or not. /// public bool IsPreRelease { get; } /// /// Gets a value indicating whether semantic version has meta or not. /// public bool HasMeta { get; } /// /// Gets the VersionString of the semantic version. /// public string VersionString { get; } /// /// Gets the AssemblyVersion of the semantic version. /// public Version AssemblyVersion { get; } /// /// Initializes a new instance of the class. /// /// Major number. /// Minor number. /// Patch number. /// Prerelease string. /// Meta string. public SemVersion(int major, int minor, int patch, string preRelease = null, string meta = null) : this(major, minor, patch, preRelease, meta, null) { } /// /// Initializes a new instance of the class. /// /// Major number. /// Minor number. /// Patch number. /// Prerelease string. /// Meta string. /// The complete version number. public SemVersion(int major, int minor, int patch, string preRelease, string meta, string versionString) { Major = major; Minor = minor; Patch = patch; AssemblyVersion = new Version(major, minor, patch); IsPreRelease = !string.IsNullOrEmpty(preRelease); HasMeta = !string.IsNullOrEmpty(meta); PreRelease = IsPreRelease ? preRelease : null; Meta = HasMeta ? meta : null; if (!string.IsNullOrEmpty(versionString)) { VersionString = versionString; } else { var sb = new StringBuilder(); sb.AppendFormat(CultureInfo.InvariantCulture, "{0}.{1}.{2}", Major, Minor, Patch); if (IsPreRelease) { sb.AppendFormat(CultureInfo.InvariantCulture, "-{0}", PreRelease); } if (HasMeta) { sb.AppendFormat(CultureInfo.InvariantCulture, "+{0}", Meta); } VersionString = sb.ToString(); } } /// /// Method which tries to parse a semantic version string. /// /// the version that should be parsed. /// the out parameter the parsed version should be stored in. /// Returns a boolean indicating if the parse was successful. public static bool TryParse(string version, out SemVersion semVersion) { semVersion = Zero; if (string.IsNullOrEmpty(version)) { return false; } var match = SemVerRegex.Match(version); if (!match.Success) { return false; } if (!int.TryParse( match.Groups["Major"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var major) || !int.TryParse( match.Groups["Minor"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var minor) || !int.TryParse( match.Groups["Patch"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var patch)) { return false; } semVersion = new SemVersion( major, minor, patch, match.Groups["PreRelease"]?.Value, match.Groups["Meta"]?.Value, version); return true; } /// /// Checks if two SemVersion objects are equal. /// /// the other SemVersion want to test equality to. /// A boolean indicating whether the objecst we're equal or not. public bool Equals(SemVersion other) { return other is object && Major == other.Major && Minor == other.Minor && Patch == other.Patch && string.Equals(PreRelease, other.PreRelease, StringComparison.OrdinalIgnoreCase) && string.Equals(Meta, other.Meta, StringComparison.OrdinalIgnoreCase); } /// /// Compares to SemVersion objects to and another. /// /// The SemVersion object we compare with. /// Return 0 if the objects are identical, 1 if the version is newer and -1 if the version is older. public int CompareTo(SemVersion other) { if (other is null) { return 1; } if (Equals(other)) { return 0; } 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; } if (IsPreRelease != other.IsPreRelease) { return other.IsPreRelease ? 1 : -1; } switch (StringComparer.InvariantCultureIgnoreCase.Compare(PreRelease, other.PreRelease)) { case 1: return 1; case -1: return -1; default: { return (string.IsNullOrEmpty(Meta) != string.IsNullOrEmpty(other.Meta)) ? string.IsNullOrEmpty(Meta) ? 1 : -1 : StringComparer.InvariantCultureIgnoreCase.Compare(Meta, other.Meta); } } } /// /// Compares to SemVersion objects to and another. /// /// The object we compare with. /// Return 0 if the objects are identical, 1 if the version is newer and -1 if the version is older. public int CompareTo(object obj) { return (obj is SemVersion semVersion) ? CompareTo(semVersion) : -1; } /// /// Equals-method for the SemVersion class. /// /// the other SemVersion want to test equality to. /// A boolean indicating whether the objecst we're equal or not. public override bool Equals(object obj) { return (obj is SemVersion semVersion) && Equals(semVersion); } /// /// Method for getting the hashcode of the SemVersion object. /// /// The hashcode of the SemVersion object. public override int GetHashCode() { unchecked { var hashCode = Major; hashCode = (hashCode * 397) ^ Minor; hashCode = (hashCode * 397) ^ Patch; hashCode = (hashCode * 397) ^ (PreRelease != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(PreRelease) : 0); hashCode = (hashCode * 397) ^ (Meta != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Meta) : 0); return hashCode; } } /// /// Returns the string representation of an SemVersion object. /// /// The string representation of the object. public override string ToString() { int[] verParts = { Major, Minor, Patch }; string ver = string.Join(".", verParts); return $"{ver}{(IsPreRelease ? "-" : string.Empty)}{PreRelease}{Meta}"; } /// /// The greater than-operator for the SemVersion class. /// /// first SemVersion. /// second. SemVersion. /// A value indicating if the operand1 was greater than operand2. public static bool operator >(SemVersion operand1, SemVersion operand2) => operand1 is { } && operand1.CompareTo(operand2) == 1; /// /// The less than-operator for the SemVersion class. /// /// first SemVersion. /// second. SemVersion. /// A value indicating if the operand1 was less than operand2. public static bool operator <(SemVersion operand1, SemVersion operand2) => operand1 is { } ? operand1.CompareTo(operand2) == -1 : operand2 is { }; /// /// The greater than or equal to-operator for the SemVersion class. /// /// first SemVersion. /// second. SemVersion. /// A value indicating if the operand1 was greater than or equal to operand2. public static bool operator >=(SemVersion operand1, SemVersion operand2) => operand1 is { } ? operand1.CompareTo(operand2) >= 0 : operand2 is null; /// /// The lesser than or equal to-operator for the SemVersion class. /// /// first SemVersion. /// second. SemVersion. /// A value indicating if the operand1 was lesser than or equal to operand2. public static bool operator <=(SemVersion operand1, SemVersion operand2) => operand1 is null || operand1.CompareTo(operand2) <= 0; /// /// The equal to-operator for the SemVersion class. /// /// first SemVersion. /// second. SemVersion. /// A value indicating if the operand1 was equal to operand2. public static bool operator ==(SemVersion operand1, SemVersion operand2) => operand1?.Equals(operand2) ?? operand2 is null; /// /// The not equal to-operator for the SemVersion class. /// /// first SemVersion. /// second. SemVersion. /// A value indicating if the operand1 was not equal to operand2. public static bool operator !=(SemVersion operand1, SemVersion operand2) => !(operand1?.Equals(operand2) ?? operand2 is null); }