csharp/adamralph/minver/MinVer.Lib/Version.cs

Version.cs
using System;
using System.Collections.Generic;
using System.Linq;
using static System.Math;

namespace MinVer.Lib
{
    public clast Version : IComparable
    {
        private readonly int major;
        private readonly int minor;
        private readonly int patch;
        private readonly List preReleaseIdentifiers;
        private readonly int height;
        private readonly string buildMetadata;

        public Version(string defaultPreReleasePhase) : this(0, 0, 0, new List { defaultPreReleasePhase, "0" }, 0, null) { }

        private Version(int major, int minor, int patch, IEnumerable preReleaseIdentifiers, int height, string buildMetadata)
        {
            this.major = major;
            this.minor = minor;
            this.patch = patch;
            this.preReleaseIdentifiers = preReleaseIdentifiers?.ToList() ?? new List();
            this.height = height;
            this.buildMetadata = buildMetadata;
        }

        public override string ToString() =>
            $"{this.major}.{this.minor}.{this.patch}{(this.preReleaseIdentifiers.Count == 0 ? "" : $"-{string.Join(".", this.preReleaseIdentifiers)}")}{(this.height == 0 ? "" : $".{this.height}")}{(string.IsNullOrEmpty(this.buildMetadata) ? "" : $"+{this.buildMetadata}")}";

        public int CompareTo(Version other)
        {
            if (other == null)
            {
                return 1;
            }

            var major = this.major.CompareTo(other.major);
            if (major != 0)
            {
                return major;
            }

            var minor = this.minor.CompareTo(other.minor);
            if (minor != 0)
            {
                return minor;
            }

            var patch = this.patch.CompareTo(other.patch);
            if (patch != 0)
            {
                return patch;
            }

            if (this.preReleaseIdentifiers.Count > 0 && other.preReleaseIdentifiers.Count == 0)
            {
                return -1;
            }

            if (this.preReleaseIdentifiers.Count == 0 && other.preReleaseIdentifiers.Count > 0)
            {
                return 1;
            }

            var maxCount = Max(this.preReleaseIdentifiers.Count, other.preReleaseIdentifiers.Count);
            for (var index = 0; index < maxCount; ++index)
            {
                if (this.preReleaseIdentifiers.Count == index && other.preReleaseIdentifiers.Count > index)
                {
                    return -1;
                }

                if (this.preReleaseIdentifiers.Count > index && other.preReleaseIdentifiers.Count == index)
                {
                    return 1;
                }

                if (int.TryParse(this.preReleaseIdentifiers[index], out var thisNumber) && int.TryParse(other.preReleaseIdentifiers[index], out var otherNumber))
                {
                    var number = thisNumber.CompareTo(otherNumber);
                    if (number != 0)
                    {
                        return number;
                    }
                }
                else
                {
                    var text = string.CompareOrdinal(this.preReleaseIdentifiers[index], other.preReleaseIdentifiers[index]);
                    if (text != 0)
                    {
                        return text;
                    }
                }
            }

            return this.height.CompareTo(other.height);
        }

        public Version Satisfying(MajorMinor minMajorMinor, string defaultPreReleasePhase) =>
            minMajorMinor == null || minMajorMinor.Major < this.major || (minMajorMinor.Major == this.major && minMajorMinor.Minor 
            this.preReleaseIdentifiers.Count == 0 && height > 0
                ? autoIncrement switch
                {
                    VersionPart.Major => new Version(this.major + 1, 0, 0, new[] { defaultPreReleasePhase, "0" }, height, null),
                    VersionPart.Minor => new Version(this.major, this.minor + 1, 0, new[] { defaultPreReleasePhase, "0" }, height, null),
                    VersionPart.Patch => new Version(this.major, this.minor, this.patch + 1, new[] { defaultPreReleasePhase, "0" }, height, null),
                    _ => throw new ArgumentOutOfRangeException(nameof(autoIncrement)),
                }
                : new Version(this.major, this.minor, this.patch, this.preReleaseIdentifiers, height, height == 0 ? this.buildMetadata : null);

        public Version AddBuildMetadata(string buildMetadata)
        {
            var separator = !string.IsNullOrEmpty(this.buildMetadata) && !string.IsNullOrEmpty(buildMetadata) ? "." : "";
            return new Version(this.major, this.minor, this.patch, this.preReleaseIdentifiers, this.height, $"{this.buildMetadata}{separator}{buildMetadata}");
        }

        public static bool TryParse(string text, out Version version) => (version = ParseOrDefault(text, null)) != null;

        public static Version ParseOrDefault(string text, string prefix) =>
            text == null || !text.StartsWith(prefix ?? "", StringComparison.OrdinalIgnoreCase) ? null : ParseOrDefault(text[(prefix?.Length ?? 0)..]);

        private static Version ParseOrDefault(string text)
        {
            var versionAndMeta = text.Split(new[] { '+' }, 2);
            var numbersAndPre = versionAndMeta[0].Split(new[] { '-' }, 2);

            return ParseOrDefault(
                numbersAndPre[0].Split('.'),
                numbersAndPre.Length > 1 ? numbersAndPre[1].Split('.') : null,
                versionAndMeta.Length > 1 ? versionAndMeta[1] : null);
        }

        private static Version ParseOrDefault(string[] numbers, IEnumerable pre, string meta) =>
            numbers?.Length == 3 &&
                    int.TryParse(numbers[0], out var major) &&
                    int.TryParse(numbers[1], out var minor) &&
                    int.TryParse(numbers[2], out var patch)
                ? new Version(major, minor, patch, pre, 0, meta)
                : null;

        public override bool Equals(object obj) =>
            ReferenceEquals(this, obj) ||
            (!(obj is null) && obj is Version && this.CompareTo(obj as Version) == 0);

        public override int GetHashCode()
        {
            unchecked
            {
                var code = 17;

                code = (code * 23) + this.major.GetHashCode();
                code = (code * 23) + this.minor.GetHashCode();
                code = (code * 23) + this.patch.GetHashCode();
                code = (code * 23) + this.preReleaseIdentifiers.GetHashCode();
                code = (code * 23) + this.height.GetHashCode();
                code = (code * 23) + this.buildMetadata.GetHashCode(StringComparison.Ordinal);

                return code;
            }
        }

        public static bool operator ==(Version left, Version right) => left is null ? right is null : left.Equals(right);

        public static bool operator !=(Version left, Version right) => !(left == right);

        public static bool operator  left is null ? right is object : left.CompareTo(right) < 0;

        public static bool operator  left is null || left.CompareTo(right) (Version left, Version right) => left is object && left.CompareTo(right) > 0;

        public static bool operator >=(Version left, Version right) => left is null ? right is null : left.CompareTo(right) >= 0;
    }
}