public static bool TryParse(string text, [NotNullWhen(true)] out VersionInfo?version)
        {
            // check for version suffix (e.g "-rc1")
            var    index = text.IndexOf('-');
            string versionText;
            string suffix;

            if (index < 0)
            {
                versionText = text;
                suffix      = "";
            }
            else
            {
                versionText = text.Substring(0, index);
                suffix      = text.Substring(index).TrimEnd('*');
            }

            // parse parts of the version number
            int major;
            int minor;
            int build;
            int revision;

            if (int.TryParse(versionText, out major))
            {
                // version is a single integral major version number
                minor    = -1;
                build    = -1;
                revision = -1;
            }
            else if (Version.TryParse(versionText, out var basicVersion))
            {
                // assign parts of the parsed version information
                major    = basicVersion.Major;
                minor    = basicVersion.Minor;
                build    = basicVersion.Build;
                revision = basicVersion.Revision;
            }
            else
            {
                version = null;
                return(false);
            }

            // if major version is 0 and minor is not 0, we encode the value as a fractional major version number
            if ((major == 0) && (minor != 0))
            {
                var fractionalMajor = int.MinValue + minor - 1;
                version = new VersionInfo(
                    fractionalMajor,
                    (build != -1) ? (int?)build : null,
                    (revision != -1) ? (int?)revision : null,
                    suffix
                    );
                return(true);
            }

            // revision is not supported in this format since it requires 4 numbers to be stored
            if (revision != -1)
            {
                version = null;
                return(false);
            }
            version = new VersionInfo(
                major,
                (minor != -1) ? (int?)minor : null,
                (build != -1) ? (int?)build : null,
                suffix
                );
            return(true);
        }
 public bool IsGreaterOrEqualThanVersion(VersionInfo info, bool strict = false) => CompareToVersion(info, strict) >= 0;
 public bool IsEqualToVersion(VersionInfo info, bool strict            = false) => CompareToVersion(info, strict) == 0;
        public int?CompareToVersion(VersionInfo other, bool strict = false)
        {
            if (object.ReferenceEquals(other, null))
            {
                return(null);
            }
            if (strict)
            {
                // suffixes must match to be comparable in strict mode
                if (Suffix != other.Suffix)
                {
                    return(null);
                }
                var result = (long)EncodedMajor - (long)other.EncodedMajor;
                if (result != 0)
                {
                    return(Sign(result));
                }
                result = (Minor ?? 0) - (other.Minor ?? 0);
                if (result != 0)
                {
                    return(Sign(result));
                }
                result = (Patch ?? 0) - (other.Patch ?? 0);
                if (result != 0)
                {
                    return(Sign(result));
                }
                return(0);
            }
            else
            {
                // version number dominates other comparisions
                var result = (long)EncodedMajor - (long)other.EncodedMajor;
                if (result != 0)
                {
                    return(Sign(result));
                }
                result = (Minor ?? 0) - (other.Minor ?? 0);
                if (result != 0)
                {
                    return(Sign(result));
                }
                result = (Patch ?? 0) - (other.Patch ?? 0);
                if (result != 0)
                {
                    return(Sign(result));
                }

                // a suffix indicates a pre-release version, which is always less than the stable version
                if ((Suffix == "") && (other.Suffix != ""))
                {
                    return(1);
                }
                if ((Suffix != "") && (other.Suffix == ""))
                {
                    return(-1);
                }
                if (Suffix == other.Suffix)
                {
                    return(0);
                }

                // check if the suffixes have a trailing number that can be compared
                var shortestLength = Math.Min(Suffix.Length, other.Suffix.Length);
                var i = 1;
                for (; (i < shortestLength) && char.IsLetter(Suffix[i]) && (Suffix[i] == other.Suffix[i]); ++i)
                {
                    ;
                }
                if (
                    int.TryParse(Suffix.Substring(i, Suffix.Length - i), out var leftSuffixValue) &&
                    int.TryParse(other.Suffix.Substring(i, other.Suffix.Length - i), out var rightSuffixVersion)
                    )
                {
                    if (leftSuffixValue > rightSuffixVersion)
                    {
                        return(1);
                    }
                    if (leftSuffixValue < rightSuffixVersion)
                    {
                        return(-1);
                    }
                    return(0);
                }
            }

            // versions cannot be compared
            return(null);

            // local functions
            int Sign(long value) => (value < 0L) ? -1 : ((value > 0L) ? 1 : 0);
        }
 public bool IsLessOrEqualThanVersion(VersionInfo info, bool strict    = false) => CompareToVersion(info, strict) <= 0;
 public static VersionInfo GetLambdaSharpAssemblyReferenceVersion(VersionInfo version) => version.GetMajorMinorVersion();
        public static VersionInfo?FindLatestMatchingVersion(IEnumerable <VersionInfo> versionInfos, VersionInfo minVersion, Predicate <VersionInfo> validate)
        {
            var candidates = new List <VersionInfo>(versionInfos);

            while (candidates.Any())
            {
                // find latest version
                var candidate = VersionInfo.Max(candidates) ?? throw new InvalidOperationException();
                candidates.Remove(candidate);

                // check if latest version meets minimum version constraint; or if none are provided, the version cannot be a pre-release
                if (
                    ((minVersion != null) && minVersion.IsGreaterThanVersion(candidate)) ||
                    ((minVersion == null) && (validate == null) && candidate.IsPreRelease())
                    )
                {
                    continue;
                }

                // validate candidate
                if (validate?.Invoke(candidate) ?? true)
                {
                    return(candidate);
                }
            }

            // not match found
            return(null);
        }
 public static VersionInfo GetCoreServicesReferenceVersion(VersionInfo version)
 => version.GetMajorOnlyVersion();
 public static bool IsModuleCoreVersionCompatibleWithTierVersion(VersionInfo moduleCoreVersion, VersionInfo tierVersion)
 => moduleCoreVersion.GetMajorOnlyVersion().IsEqualToVersion(tierVersion.GetMajorOnlyVersion());
 public static bool IsModuleCoreVersionCompatibleWithToolVersion(VersionInfo moduleCoreVersion, VersionInfo toolVersion)
 => toolVersion.IsPreRelease()
         ? moduleCoreVersion.GetMajorOnlyVersion().WithoutSuffix().IsEqualToVersion(toolVersion.GetMajorOnlyVersion().WithoutSuffix()) && moduleCoreVersion.IsLessOrEqualThanVersion(toolVersion)
         : moduleCoreVersion.GetMajorOnlyVersion().IsEqualToVersion(toolVersion.GetMajorOnlyVersion());
        //--- Class Methods ---
        public static bool IsTierVersionCompatibleWithToolVersion(VersionInfo tierVersion, VersionInfo toolVersion)
        => toolVersion.IsPreRelease()

        // allow a tool pre-release, next version to be compatible with any previously deployed tier version
                ? tierVersion.GetMajorOnlyVersion().WithoutSuffix().IsEqualToVersion(toolVersion.GetMajorOnlyVersion().WithoutSuffix()) && tierVersion.IsLessOrEqualThanVersion(toolVersion)

        // tool major version must match tier major version
                : tierVersion.GetMajorOnlyVersion().IsEqualToVersion(toolVersion.GetMajorOnlyVersion());
        public static bool IsValidLambdaSharpAssemblyReferenceForToolVersion(VersionInfo toolVersion, string projectFramework, string lambdaSharpAssemblyVersion, out bool outdated)
        {
            // extract assembly version pattern without wildcard
            VersionWithSuffix libraryVersion;

            if (lambdaSharpAssemblyVersion.EndsWith(".*", StringComparison.Ordinal))
            {
                libraryVersion = VersionWithSuffix.Parse(lambdaSharpAssemblyVersion.Substring(0, lambdaSharpAssemblyVersion.Length - 2));
            }
            else
            {
                libraryVersion = VersionWithSuffix.Parse(lambdaSharpAssemblyVersion);
            }

            // compare based on selected framework
            bool valid;

            switch (projectFramework)
            {
            case "netstandard2.1":

                // .NET Standard 2.1 projects (Blazor) require 0.8.1.* or 0.8.2.*
                valid = (libraryVersion.Major == 0) &&
                        (libraryVersion.Minor == 8) &&
                        (
                    (libraryVersion.Build == 1) ||
                    (libraryVersion.Build == 2)
                        );
                break;

            case "netcoreapp2.1":

                // .NET Core 2.1 projects (Lambda) require 0.8.0.* or 0.8.1.*
                valid = (libraryVersion.Major == 0) &&
                        (libraryVersion.Minor == 8) &&
                        (
                    (libraryVersion.Build == 0) ||
                    (libraryVersion.Build == 1)
                        );
                break;

            case "netcoreapp3.1":

                // .NET Core 3.1 projects (Lambda) require 0.8.0.*, 0.8.1.*, or 0.8.2.*
                valid = (libraryVersion.Major == 0) &&
                        (libraryVersion.Minor == 8) &&
                        (
                    (libraryVersion.Build == 0) ||
                    (libraryVersion.Build == 1) ||
                    (libraryVersion.Build == 2)
                        );
                break;

            case "net5":
            case "net5.0":

                // .NET 5 projects require 0.8.2.*
                valid = (libraryVersion.Major == 0) &&
                        (libraryVersion.Minor == 8) &&
                        (libraryVersion.Build == 2);
                break;

            default:
                throw new VersionInfoCompatibilityUnsupportedFrameworkException(projectFramework);
            }
            outdated = valid && VersionInfo.From(libraryVersion, strict: false).IsLessThanVersion(toolVersion.GetMajorMinorVersion());
            return(valid);
        }