Class/SemVer.cs

using System;
using System.Text;
using System.Text.RegularExpressions;
 
namespace pspm
{
    //:------------------------:
    #region SemVer
    //:------------------------:
    public class SemVer : IComparable, IComparable<SemVer>, IEquatable<SemVer>
    {
        private int _Major;
        private int _Minor = 0;
        private int _Patch = 0;
        private int _Revision = 0;
        private string _PreReleaseLabel = null;
        private string _BuildLabel = null;
 
        private static readonly Regex LabelValidator = new Regex(@"^[.0-9A-Za-z-]*$");
 
        //static member
        public static SemVer Max = new SemVer(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue);
        public static SemVer Min = new SemVer(0);
 
        public int Major
        {
            get { return this._Major; }
 
            private set
            {
                if (value >= 0 && value <= int.MaxValue)
                {
                    this._Major = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException($"{nameof(Major)} should be between 0 and {int.MaxValue.ToString()}");
                }
            }
        }
 
        public int Minor
        {
            get { return this._Minor; }
 
            private set
            {
                if (value >= 0 && value <= int.MaxValue)
                {
                    this._Minor = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException($"{nameof(Minor)} should be between 0 and {int.MaxValue.ToString()}");
                }
            }
        }
 
        public int Patch
        {
            get { return this._Patch; }
 
            private set
            {
                if (value >= 0 && value <= int.MaxValue)
                {
                    this._Patch = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException($"{nameof(Patch)} should be between 0 and {int.MaxValue.ToString()}");
                }
            }
        }
 
        public int Revision
        {
            get { return this._Revision; }
 
            private set
            {
                if (value >= 0 && value <= int.MaxValue)
                {
                    this._Revision = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException($"{nameof(Revision)} should be between 0 and {int.MaxValue.ToString()}");
                }
            }
        }
 
        public string PreReleaseLabel
        {
            get { return this._PreReleaseLabel; }
 
            private set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    this._PreReleaseLabel = null;
                }
                else if (LabelValidator.IsMatch(value))
                {
                    this._PreReleaseLabel = value;
                }
                else
                {
                    throw new ArgumentException($"{nameof(PreReleaseLabel)} contains invalid character");
                }
            }
        }
 
        public string BuildLabel
        {
            get { return this._BuildLabel; }
 
            private set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    this._BuildLabel = null;
                }
                else if (LabelValidator.IsMatch(value))
                {
                    this._BuildLabel = value;
                }
                else
                {
                    throw new ArgumentException($"{nameof(BuildLabel)} contains invalid character");
                }
            }
        }
 
        // Constructor
        public SemVer(int major) : this(major, 0, 0, 0, null, null) { }
 
        public SemVer(int major, int minor) : this(major, minor, 0, 0, null, null) { }
 
        public SemVer(int major, int minor, int patch) : this(major, minor, patch, 0, null, null) { }
 
        public SemVer(int major, int minor, int patch, int revision) : this(major, minor, patch, revision, null, null) { }
 
        public SemVer(int major, int minor, int patch, int revision, string prerelease) : this(major, minor, patch, revision, prerelease, null) { }
 
        public SemVer(int major, int minor, int patch, int revision, string prerelease, string build)
        {
            this.Major = major;
            this.Minor = minor;
            this.Patch = patch;
            this.Revision = revision;
            this.PreReleaseLabel = prerelease;
            this.BuildLabel = build;
        }
 
        public SemVer(Version version)
        {
            this.Major = (version.Major >= 0) ? version.Major : 0;
            this.Minor = (version.Minor >= 0) ? version.Minor : 0;
            this.Patch = (version.Build >= 0) ? version.Build : 0;
            this.Revision = (version.Revision >= 0) ? version.Revision : 0;
        }
 
        public SemVer(string expression)
        {
            SemVer semver = SemVer.Parse(expression);
            this.Major = semver.Major;
            this.Minor = semver.Minor;
            this.Patch = semver.Patch;
            this.Revision = semver.Revision;
            this.PreReleaseLabel = semver.PreReleaseLabel;
            this.BuildLabel = semver.BuildLabel;
        }
 
 
        // ToString()
        public override string ToString()
        {
            StringBuilder result = new StringBuilder();
 
            result.Append(this.Major).Append(".").Append(this.Minor).Append(".").Append(this.Patch);
 
            if (this.Revision > 0)
            {
                result.Append(".").Append(this.Revision);
            }
 
            if (!string.IsNullOrEmpty(this.PreReleaseLabel))
            {
                result.Append("-").Append(this.PreReleaseLabel);
            }
 
            if (!string.IsNullOrEmpty(this.BuildLabel))
            {
                result.Append("+").Append(this.BuildLabel);
            }
 
            return result.ToString();
        }
 
 
        // IsPrerelease()
        public bool IsPrerelease()
        {
            return !string.IsNullOrEmpty(_PreReleaseLabel);
        }
 
 
        // Parse()
        public static SemVer Parse(string expression)
        {
            //Ignore first "=" or "v" e.g) v=1.0.0 -> 1.0.0
            var p = new Regex(@"^[=v]+", RegexOptions.IgnoreCase);
            expression = (p.IsMatch(expression)) ? p.Replace(expression, string.Empty) : expression;
 
            //split major.minor.patch
            string[] numbers = expression.Split(new char[] { '-', '+' })[0].Split('.');
 
            int tMinor, tMajor, tPatch, tRevision;
            string tPreReleaseLabel = null, tBuildLabel = null;
 
            if (int.TryParse(numbers[0], out tMajor))
            {
                if (tMajor < 0)
                {
                    throw new FormatException();
                }
            }
            else
            {
                throw new FormatException();
            }
 
            if (numbers.Length <= 1)
            {
                tMinor = 0;
            }
            else if (int.TryParse(numbers[1], out tMinor))
            {
                if (tMinor < 0)
                {
                    throw new FormatException();
                }
            }
            else
            {
                throw new FormatException();
            }
 
            if (numbers.Length <= 2)
            {
                tPatch = 0;
            }
            else if (int.TryParse(numbers[2], out tPatch))
            {
                if (tPatch < 0)
                {
                    throw new FormatException();
                }
            }
            else
            {
                throw new FormatException();
            }
 
            if (numbers.Length <= 3)
            {
                tRevision = 0;
            }
            else if (int.TryParse(numbers[3], out tRevision))
            {
                if (tRevision < 0)
                {
                    throw new FormatException();
                }
            }
            else
            {
                throw new FormatException();
            }
 
            //split prelease+buildmeta
            string prerelease = "";
            int indexI = expression.IndexOf("-");
            if (indexI >= 1)
            {
                prerelease = expression.Substring(indexI + 1);
            }
 
            string build = "";
            int indexJ = expression.IndexOf("+");
            if (indexJ >= 1)
            {
                build = expression.Substring(indexJ + 1);
            }
 
            if (prerelease.Length > build.Length)
            {
                if (!string.IsNullOrEmpty(prerelease))
                {
                    var tmp = prerelease.Split('+');
                    tPreReleaseLabel = tmp[0].Trim();
                    if (tmp.Length > 1)
                    {
                        tBuildLabel = tmp[1].Trim();
                    }
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(build))
                {
                    tBuildLabel = build.Trim();
                }
 
            }
 
            return new SemVer(tMajor, tMinor, tPatch, tRevision, tPreReleaseLabel, tBuildLabel);
        }
 
 
        // TryParse()
        public static bool TryParse(string expression, out SemVer result)
        {
            try
            {
                result = SemVer.Parse(expression);
                return true;
            }
            catch (Exception)
            {
                result = null;
                return false;
            }
        }
 
 
        public int CompareTo(object obj)
        {
            if (obj == null) { return 1; }
 
            var v = obj as SemVer;
            if (v == null)
            {
                throw new ArgumentException(nameof(obj));
            }
 
            return CompareTo(v);
        }
 
 
        public int CompareTo(SemVer semver)
        {
            if ((object)semver == null) { return 1; }
 
            // Compare Major
            if (this.Major != semver.Major)
            {
                return (this.Major > semver.Major) ? 1 : -1;
            }
 
            // Compare Minor
            else if (this.Minor != semver.Minor)
            {
                return (this.Minor > semver.Minor) ? 1 : -1;
            }
 
            // Compare Patch
            else if (this.Patch != semver.Patch)
            {
                return (this.Patch > semver.Patch) ? 1 : -1;
            }
 
            // Compare Revision
            else if (this.Revision != semver.Revision)
            {
                return (this.Revision > semver.Revision) ? 1 : -1;
            }
 
            // Compare PrereleaseLabel
 
            // pre-release version has lower precedence than a normal version
            else if (string.IsNullOrEmpty(this.PreReleaseLabel))
            {
                return String.IsNullOrEmpty(semver.PreReleaseLabel) ? 0 : 1;
            }
 
            else if (string.IsNullOrEmpty(semver.PreReleaseLabel))
            {
                return -1;
            }
 
            else
            {
                string[] identifierMyself = this.PreReleaseLabel.Split('.');
                string[] identifierTarget = semver.PreReleaseLabel.Split('.');
                int minLength = Math.Min(identifierMyself.Length, identifierTarget.Length);
 
                for (int i = 0; i < minLength; i++)
                {
                    var my = identifierMyself[i];
                    var tr = identifierTarget[i];
                    bool isNum_my = int.TryParse(my, out int num_my);
                    bool isNum_tr = int.TryParse(tr, out int num_tr);
 
                    // identifiers consisting of only digits are compared numerically
                    if (isNum_my && isNum_tr)
                    {
                        if (num_my == num_tr) { continue; }
                        else { return ((num_my < num_tr) ? -1 : 1); }
                    }
                    // Numeric identifiers always have lower precedence than non-numeric identifiers
                    else if (isNum_my)
                    {
                        return -1;
                    }
                    else if (isNum_tr)
                    {
                        return 1;
                    }
                    // identifiers with letters or hyphens are compared lexically in ASCII sort order.
                    else
                    {
                        if (my.Equals(tr, StringComparison.Ordinal)) { continue; }
                        else { return string.CompareOrdinal(my, tr); }
                    }
                }
            }
 
            return 0;
        }
 
 
        public static int Compare(SemVer ver1, SemVer ver2)
        {
            if (ver1 != null) { return ver1.CompareTo(ver2); }
            if (ver2 != null) { return -1; }
 
            return 0;
        }
 
 
        public override bool Equals(object obj) => this.Equals(obj as SemVer);
 
 
        public bool Equals(SemVer other)
        {
            // If parameter is null, return false
            if (other == null) { return false; }
 
            // Optimization for a common success case.
            if (Object.ReferenceEquals(this, other)) { return true; }
 
            return (
                // SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).
                (this.Major == other.Major) &&
                (this.Minor == other.Minor) &&
                (this.Patch == other.Patch) &&
                (this.Revision == other.Revision) &&
                string.Equals(this.PreReleaseLabel, other.PreReleaseLabel, StringComparison.Ordinal)
            );
        }
 
 
        public override int GetHashCode() => this.ToString().GetHashCode();
 
 
        //Operator override
        public static bool operator ==(SemVer ver1, SemVer ver2)
        {
            if ((object)ver1 == null)
            {
                return ((object)ver2 == null);
            }
 
            if ((object)ver2 == null)
            {
                return false;
            }
 
            return ver1.Equals(ver2);
        }
 
        public static bool operator !=(SemVer ver1, SemVer ver2)
        {
            return !(ver1 == ver2);
        }
 
        public static bool operator <(SemVer ver1, SemVer ver2)
        {
            if ((object)ver1 == null || (object)ver2 == null)
            {
                throw new ArgumentNullException();
            }
 
            return (ver1.CompareTo(ver2) < 0);
        }
 
        public static bool operator >(SemVer ver1, SemVer ver2)
        {
            return (ver2 < ver1);
        }
 
        public static bool operator <=(SemVer ver1, SemVer ver2)
        {
            if ((object)ver1 == null || (object)ver2 == null)
            {
                throw new ArgumentNullException();
            }
 
            return (ver1.CompareTo(ver2) <= 0);
        }
 
        public static bool operator >=(SemVer ver1, SemVer ver2)
        {
            return (ver2 <= ver1);
        }
    }
    //:------------------------:
    #endregion SemVer
    //:------------------------:
}