/// Source file from ProBuilder 4.4.0 /// https://github.com/Unity-Technologies/com.unity.probuilder/blob/master/Runtime/Core/SemVer.cs using System; using System.Text; using System.Text.RegularExpressions; using UnityEngine; namespace UnityEditor.Sequences { /// /// Version information container that is comparable. /// [Serializable] sealed class SemanticVersion : IEquatable, IComparable, IComparable { [SerializeField] int m_Major = -1; [SerializeField] int m_Minor = -1; [SerializeField] int m_Patch = -1; [SerializeField] int m_Build = -1; [SerializeField] string m_Type; [SerializeField] string m_Metadata; [SerializeField] string m_Date; public int major { get { return m_Major; } } public int minor { get { return m_Minor; } } public int patch { get { return m_Patch; } } public int build { get { return m_Build; } } public string type { get { return m_Type != null ? m_Type : ""; } } public string metadata { get { return m_Metadata != null ? m_Metadata : ""; } } public string date { get { return m_Date != null ? m_Date : ""; } } /// /// Get a new version info with just the major, minor, and patch values. /// public SemanticVersion MajorMinorPatch { get { return new SemanticVersion(major, minor, patch); } } public const string DefaultStringFormat = "M.m.p-t.b"; public SemanticVersion() { m_Major = 0; m_Minor = 0; m_Patch = 0; m_Build = -1; m_Type = null; m_Date = null; m_Metadata = null; } public SemanticVersion(string formatted, string date = null) { SemanticVersion parsed; m_Metadata = formatted; m_Date = date; if (TryGetVersionInfo(formatted, out parsed)) { m_Major = parsed.m_Major; m_Minor = parsed.m_Minor; m_Patch = parsed.m_Patch; m_Build = parsed.m_Build; m_Type = parsed.m_Type; m_Metadata = parsed.metadata; } #if PB_DEBUG else { Log.Error("Failed parsing version info: " + formatted); } #endif } public SemanticVersion(int major, int minor, int patch, int build = -1, string type = null, string date = null, string metadata = null) { m_Major = major; m_Minor = minor; m_Patch = patch; m_Build = build; m_Type = type; m_Metadata = metadata; m_Date = date; } public bool IsValid() { return major != -1 && minor != -1 && patch != -1; } public override bool Equals(object o) { return o is SemanticVersion && Equals((SemanticVersion)o); } public override int GetHashCode() { int hash = 13; unchecked { if (IsValid()) { hash = (hash * 7) + major.GetHashCode(); hash = (hash * 7) + minor.GetHashCode(); hash = (hash * 7) + patch.GetHashCode(); hash = (hash * 7) + build.GetHashCode(); hash = (hash * 7) + type.GetHashCode(); } else { return string.IsNullOrEmpty(metadata) ? metadata.GetHashCode() : base.GetHashCode(); } } return hash; } public bool Equals(SemanticVersion version) { if (object.ReferenceEquals(version, null)) return false; if (IsValid() != version.IsValid()) return false; if (IsValid()) { return major == version.major && minor == version.minor && patch == version.patch && type.Equals(version.type) && build.Equals(version.build); } else { if (string.IsNullOrEmpty(metadata) || string.IsNullOrEmpty(version.metadata)) return false; return metadata.Equals(version.metadata); } } public int CompareTo(object obj) { return CompareTo(obj as SemanticVersion); } static int WrapNoValue(int value) { return value < 0 ? int.MaxValue : value; } public int CompareTo(SemanticVersion version) { const int GREATER = 1; const int EVEN = 0; const int LESS = -1; if (object.ReferenceEquals(version, null)) return GREATER; if (Equals(version)) return EVEN; if (major > version.major) return GREATER; if (major < version.major) return LESS; if (minor > version.minor) return GREATER; if (minor < version.minor) return LESS; // missing values in the following categories are > than existing. if (WrapNoValue(patch) > WrapNoValue(version.patch)) return GREATER; if (WrapNoValue(patch) < WrapNoValue(version.patch)) return LESS; if (string.IsNullOrEmpty(type) && !string.IsNullOrEmpty(version.type)) return GREATER; if (!string.IsNullOrEmpty(type) && string.IsNullOrEmpty(version.type)) return LESS; if (WrapNoValue(build) > WrapNoValue(version.build)) return GREATER; if (WrapNoValue(build) < WrapNoValue(version.build)) return LESS; return EVEN; } public static bool operator==(SemanticVersion left, SemanticVersion right) { if (object.ReferenceEquals(left, null)) return object.ReferenceEquals(right, null); return left.Equals(right); } public static bool operator!=(SemanticVersion left, SemanticVersion right) { return !(left == right); } public static bool operator<(SemanticVersion left, SemanticVersion right) { if (object.ReferenceEquals(left, null)) return !object.ReferenceEquals(right, null); return left.CompareTo(right) < 0; } public static bool operator>(SemanticVersion left, SemanticVersion right) { // null < null still equals false if (object.ReferenceEquals(left, null)) return false; return left.CompareTo(right) > 0; } public static bool operator<=(SemanticVersion left, SemanticVersion right) { return left == right || left < right; } public static bool operator>=(SemanticVersion left, SemanticVersion right) { return left == right || left > right; } /// /// Simple formatting for a version info. The following characters are available: /// 'M' Major /// 'm' Minor /// 'p' Patch /// 'b' Build /// 'T' Type /// 'd' Date /// 'D' Metadata /// Escape characters with '\'. /// /// /// ToString("\buil\d: T:M.m.p") returns "build: Final:2.10.1" /// /// /// public string ToString(string format) { var sb = new StringBuilder(); bool skip = false; foreach (char c in format.ToCharArray()) { if (skip) { sb.Append(c); skip = false; continue; } if (c == '\\') skip = true; else if (c == 'M') sb.Append(major); else if (c == 'm') sb.Append(minor); else if (c == 'p') sb.Append(patch); else if (c == 'b') sb.Append(build); else if (c == 'T' || c == 't') sb.Append(type); else if (c == 'd') sb.Append(date); else if (c == 'D') sb.Append(metadata); else sb.Append(c); } return sb.ToString(); } /// /// Returns a string with all the information that this version contains, including date. /// /// public override string ToString() { var sb = new StringBuilder(); sb.Append(ToString("M.m.p")); if (!string.IsNullOrEmpty(type)) { sb.Append("-"); sb.Append(type); if (build > -1) { sb.Append("."); sb.Append(build.ToString()); } } if (!string.IsNullOrEmpty(date)) { sb.Append(" "); sb.Append(date); } return sb.ToString(); } /// /// Create a pb_VersionInfo type from a string formatted in valid semantic versioning format. /// https://semver.org/ /// Ex: TryGetVersionInfo("2.5.3-b.1", out info) /// /// /// /// public static bool TryGetVersionInfo(string input, out SemanticVersion version) { version = new SemanticVersion(); bool ret = false; const string k_MajorMinorPatchRegex = "^([0-9]+\\.[0-9]+\\.[0-9]+)"; const string k_VersionReleaseRegex = "(?i)(?<=\\-)[a-z0-9\\-]+"; const string k_VersionBuildRegex = "(?i)(?<=\\-[a-z0-9\\-]+\\.)[0-9]+"; const string k_MetadataRegex = "(?<=\\+).+"; try { var mmp = Regex.Match(input, k_MajorMinorPatchRegex); if (!mmp.Success) return false; string[] mmpSplit = mmp.Value.Split('.'); int.TryParse(mmpSplit[0], out version.m_Major); int.TryParse(mmpSplit[1], out version.m_Minor); int.TryParse(mmpSplit[2], out version.m_Patch); ret = true; // from here down is not required var preReleaseVersion = Regex.Match(input, k_VersionReleaseRegex); if (preReleaseVersion.Success) version.m_Type = preReleaseVersion.Value; var preReleaseBuild = Regex.Match(input, k_VersionBuildRegex); version.m_Build = preReleaseBuild.Success ? GetBuildNumber(preReleaseBuild.Value) : -1; var meta = Regex.Match(input, k_MetadataRegex); if (meta.Success) version.m_Metadata = meta.Value; } catch { ret = false; } return ret; } static int GetBuildNumber(string input) { var number = Regex.Match(input, "[0-9]+"); int buildNo = 0; if (number.Success && int.TryParse(number.Value, out buildNo)) return buildNo; return -1; } } }