Пример #1
0
        static void InlineAssertInvariants(CSVersion v)
        {
#if DEBUG
            if (!_alreadyInCheck && v.IsValid)
            {
                _alreadyInCheck = true;
                try
                {
                    if (v.IsLongForm)
                    {
                        Debug.Assert(v.NormalizedText == ComputeLongFormVersion(v.Major, v.Minor, v.Patch, v.PrereleaseNameIdx, v.PrereleaseNumber, v.PrereleasePatch, v.BuildMetaData));
                    }
                    else
                    {
                        Debug.Assert(v.NormalizedText == ComputeShortFormVersion(v.Major, v.Minor, v.Patch, v.PrereleaseNameIdx, v.PrereleaseNumber, v.PrereleasePatch, v.BuildMetaData));
                    }
                    //// Systematically checks that a valid CSVersion can be parsed back in Long or Short form.
                    Debug.Assert(SVersion.TryParse(v.ToString(CSVersionFormat.Normalized)).Equals(v.ToNormalizedForm()));
                    Debug.Assert(SVersion.TryParse(v.ToString(CSVersionFormat.LongForm)).Equals(v.ToLongForm()));
                }
                finally
                {
                    _alreadyInCheck = false;
                }
            }
#endif
        }
Пример #2
0
        /// <summary>
        /// Parses the specified string to a constrained semantic version and throws an <see cref="ArgumentException"/>
        /// it the resulting <see cref="SVersion"/> is not a <see cref="CSVersion"/> or <see cref="SVersion.IsValid"/> is false.
        /// </summary>
        /// <param name="s">The string to parse.</param>
        /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="SVersion.BuildMetaData"/> compliance.</param>
        /// <returns>The CSVersion object.</returns>
        public static CSVersion Parse(string s, bool checkBuildMetaDataSyntax = true)
        {
            SVersion sv = SVersion.TryParse(s, true, checkBuildMetaDataSyntax);

            if (!sv.IsValid)
            {
                throw new ArgumentException(sv.ErrorMessage, nameof(s));
            }
            return(sv as CSVersion ?? throw new ArgumentException("Not a CSVersion.", nameof(s)));
        }
Пример #3
0
        /// <summary>
        /// Initializes a new <see cref="InformationalVersion"/> by parsing a string.
        /// This never throws: <see cref="IsValidSyntax"/> may be false and <see cref="ParseErrorMessage"/> exposes
        /// the error message.
        /// </summary>
        /// <param name="informationalVersion">Informational version. Can be null.</param>
        public InformationalVersion(string?informationalVersion)
        {
            if ((OriginalInformationalVersion = informationalVersion) != null)
            {
                Match m = _rV7.Match(informationalVersion);
                if (!m.Success)
                {
                    m = _rV6.Match(informationalVersion);
                }
                if (!m.Success)
                {
                    m = _rOld.Match(informationalVersion);
                }
                if (m.Success)
                {
                    RawVersion = m.Groups[2].Value;
                    CommitSha  = m.Groups[3].Value;
                    var v = SVersion.TryParse(RawVersion);
                    Version = v.AsCSVersion != null?v.AsCSVersion.ToNormalizedForm() : v;

                    if (DateTime.TryParseExact(m.Groups[4].Value, "u", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime t))
                    {
                        CommitDate = t;
                        if (t.Kind != DateTimeKind.Utc)
                        {
                            ParseErrorMessage = $"The CommitDate must be Utc: {m.Groups[4].Value} must be {DateTime.SpecifyKind( t, DateTimeKind.Utc ):u}.";
                        }
                        else if (!Version.IsValid)
                        {
                            ParseErrorMessage = "The SemVersion is invalid: " + Version.ErrorMessage;
                        }
                        else if (CommitSha.Length != 40 || !CommitSha.All(IsHexDigit))
                        {
                            ParseErrorMessage = "The CommitSha is invalid (must be 40 hex digit).";
                        }
                        else
                        {
                            IsValidSyntax = true;
                        }
                    }
                    else
                    {
                        ParseErrorMessage = "The CommitDate is invalid.It must be a UTC DateTime in \"u\" format.";
                    }
                }
                else
                {
                    ParseErrorMessage = "The String to parse does not match the standard CSemVer informational version pattern.";
                }
            }
            else
            {
                ParseErrorMessage = "String to parse is null.";
            }
        }
Пример #4
0
        /// <summary>
        /// Parses the specified string to a constrained semantic version and returns a <see cref="CSVersion"/> that
        /// may not be <see cref="SVersion.IsValid"/>.
        /// </summary>
        /// <param name="s">The string to parse.</param>
        /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="SVersion.BuildMetaData"/> compliance.</param>
        /// <returns>The CSVersion object that may not be <see cref="SVersion.IsValid"/>.</returns>
        public static CSVersion TryParse(string s, bool checkBuildMetaDataSyntax = true)
        {
            SVersion sv = SVersion.TryParse(s, true, checkBuildMetaDataSyntax);

            if (sv is CSVersion v)
            {
                return(v);
            }
            Debug.Assert(sv.IsValid == (sv.ErrorMessage == null));
            return(new CSVersion(sv.ErrorMessage ?? "Not a CSVersion.", s));
        }
        /// <summary>
        /// Standard TryParse pattern that returns a boolean rather than the resulting <see cref="CSVersion"/>.
        /// See <see cref="TryParse(string,bool)"/>.
        /// </summary>
        /// <param name="s">String to parse.</param>
        /// <param name="v">Resulting version.</param>
        /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="SVersion.BuildMetaData"/> compliance.</param>
        /// <returns>True on success, false otherwise.</returns>
        public static bool TryParse(string s, out CSVersion v, bool checkBuildMetaDataSyntax = true)
        {
            v = null;
            SVersion sv = SVersion.TryParse(s, true, checkBuildMetaDataSyntax);

            if (!sv.IsValid)
            {
                return(false);
            }
            v = sv as CSVersion;
            return(v != null);
        }
Пример #6
0
        /// <summary>
        /// Gets the string version in the given format.
        /// Returns the <see cref="SVersion.ErrorMessage"/> if it is not null.
        /// </summary>
        /// <param name="f">Format to use.</param>
        /// <param name="buildInfo">Not null to generate a post-release version.</param>
        /// <returns>Formated string (or <see cref="SVersion.ErrorMessage"/> if any).</returns>
        public string ToString(CSVersionFormat f, CIBuildDescriptor?buildInfo = null)
        {
            if (ErrorMessage != null)
            {
                return(ErrorMessage);
            }
            Debug.Assert(NormalizedText != null);
            // Fast path and cache for format with no build info.
            if (buildInfo == null)
            {
                if (f == CSVersionFormat.Normalized)
                {
                    if (IsLongForm)
                    {
                        if (_cacheOtherForm == null)
                        {
                            _cacheOtherForm = ComputeShortFormVersion(Major, Minor, Patch, PrereleaseNameIdx, PrereleaseNumber, PrereleasePatch, BuildMetaData, null);
                        }
                        return(_cacheOtherForm);
                    }
                    return(NormalizedText);
                }
                if (f == CSVersionFormat.LongForm)
                {
                    if (IsLongForm)
                    {
                        return(NormalizedText);
                    }
                    if (_cacheOtherForm == null)
                    {
                        _cacheOtherForm = ComputeLongFormVersion(Major, Minor, Patch, PrereleaseNameIdx, PrereleaseNumber, PrereleasePatch, BuildMetaData, null);
                    }
                    return(_cacheOtherForm);
                }
            }
            if (f == CSVersionFormat.FileVersion)
            {
                return(ToStringFileVersion(buildInfo != null));
            }

            if (f == CSVersionFormat.LongForm)
            {
                return(ComputeLongFormVersion(Major, Minor, Patch, PrereleaseNameIdx, PrereleaseNumber, PrereleasePatch, BuildMetaData, buildInfo));
            }
            else
            {
                Debug.Assert(f == CSVersionFormat.Normalized);
                Debug.Assert(SVersion.Parse(ComputeShortFormVersion(Major, Minor, Patch, PrereleaseNameIdx, PrereleaseNumber, PrereleasePatch, BuildMetaData, buildInfo)).PackageQuality == PackageQuality.CI);
                return(ComputeShortFormVersion(Major, Minor, Patch, PrereleaseNameIdx, PrereleaseNumber, PrereleasePatch, BuildMetaData, buildInfo));
            }
        }
        /// <summary>
        /// Parses the specified string to a constrained semantic version and returns a <see cref="CSVersion"/> that
        /// may not be <see cref="SVersion.IsValid"/>.
        /// </summary>
        /// <param name="s">The string to parse.</param>
        /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="SVersion.BuildMetaData"/> compliance.</param>
        /// <returns>The CSVersion object that may not be <see cref="SVersion.IsValid"/>.</returns>
        public static CSVersion TryParse(string s, bool checkBuildMetaDataSyntax = true)
        {
            SVersion sv = SVersion.TryParse(s, true, checkBuildMetaDataSyntax);

            if (sv is CSVersion v)
            {
                return(v);
            }
            if (!sv.IsValid)
            {
                new CSVersion(sv.ErrorMessage, s);
            }
            return(new CSVersion("Not a CSVersion.", s));
        }
        static (SVersion?Version, int FMajor, int FMinor, string?Error, bool FourtPartLost) TryMatchFloatingVersion(ref ReadOnlySpan <char> s)
        {
            // Handling the marvelous "" (empty string), that is like '*'.
            if (s.Length == 0)
            {
                return(null, -1, 0, null, false);
            }
            var version = SVersion.TryParse(ref s);

            if (version.IsValid)
            {
                // If only the 3 first parts have been read: launch SkipExtraPartsAndPrereleaseIfAny on the head to skip
                // potential extra parts and prerelease.
                return(version, 0, 0, null, SkipExtraPartsAndPrereleaseIfAny(ref s));
            }
            int major, minor = -1;

            if (TryMatchXStarInt(ref s, out major))
            {
                if (major >= 0)
                {
                    if (s.Length > 0 && TryMatch(ref s, '.'))
                    {
                        if (s.Length == 0 || !TryMatchXStarInt(ref s, out minor))
                        {
                            return(null, 0, 0, "Expecting minor number or *.", false);
                        }
                        if (minor >= 0)
                        {
                            // If a fourth part caused the version parse to fail, handle it here.
                            if (s.Length > 0 && TryMatch(ref s, '.') && s.Length > 0 && TryMatchNonNegativeInt(ref s, out int patch))
                            {
                                return(SVersion.Create(major, minor, patch), 0, 0, null, SkipExtraPartsAndPrereleaseIfAny(ref s));
                            }
                        }
                    }
                }
                else
                {
                    minor = 0;
                }
                // Forgetting any trailing "X.Y.*" since it is like "X.Y".
                // Even if the npm grammar allows "3.*-alpha" or "3.1.*+meta", we ignores this: https://semver.npmjs.com/ selects nothing.
                // We consider this stupid trail as being FourthPartLost.
                return(null, major, minor, null, SkipExtraPartsAndPrereleaseIfAny(ref s));
            }
            return(null, 0, 0, version.ErrorMessage, false);
        }
Пример #9
0
 /// <summary>
 /// Builds a standard Informational version string.
 /// </summary>
 /// <param name="version">The version. Must not be null nor invalid.</param>
 /// <param name="commitSha">The SHA1 of the commit (must be 40 hex digits).</param>
 /// <param name="commitDateUtc">The commit date (must be in UTC).</param>
 /// <returns>The informational version.</returns>
 static public string BuildInformationalVersion(SVersion version, string commitSha, DateTime commitDateUtc)
 {
     if (version == null || !version.IsValid)
     {
         throw new ArgumentException(nameof(version));
     }
     if (commitSha == null || commitSha.Length != 40 || !commitSha.All(IsHexDigit))
     {
         throw new ArgumentException("Must be a 40 hex digits string.", nameof(commitSha));
     }
     if (commitDateUtc.Kind != DateTimeKind.Utc)
     {
         throw new ArgumentException("Must be a UTC date.", nameof(commitDateUtc));
     }
     return($"{version.ToNormalizedString()}/{commitSha}/{commitDateUtc:u}");
 }
        static (ParseResult Result, bool IsFloatingMinor) TryMatchRangeAlone(ref ReadOnlySpan <char> s, SVersionLock defaultBound, bool includePrerelease)
        {
            var r = TryMatchFloatingVersion(ref s);

            if (r.Error != null)
            {
                return(new ParseResult(r.Error), false);
            }

            PackageQuality quality = includePrerelease ? PackageQuality.None : PackageQuality.Stable;

            if (r.Version != null)
            {
                // As soon as a prerelease appears, this can only be an approximation (with one exception - see below) since for npm:
                //
                // "For example, the range >1.2.3-alpha.3 would be allowed to match the version 1.2.3-alpha.7, but it
                //  would not be satisfied by 3.4.5-alpha.9, even though 3.4.5-alpha.9 is technically "greater than"
                //  1.2.3-alpha.3 according to the SemVer sort rules. The version range only accepts prerelease tags
                //  on the 1.2.3 version. The version 3.4.5 would satisfy the range, because it does not have a prerelease
                //  flag, and 3.4.5 is greater than 1.2.3-alpha.7."
                //
                // We also set the MinQuality to CI (otherwise alpha.7 will not be authorized for alpha.3) regardless of the includePrerelease.
                // Moreover, if we are coming from the ~ (tilde range), the lock is on the patch, not on the minor, and this exactly matches the
                // npm behavior.
                //
                bool isApproximated = !includePrerelease;
                if (r.Version.IsPrerelease)
                {
                    quality = PackageQuality.None;
                    if (defaultBound == SVersionLock.LockMinor)
                    {
                        defaultBound   = SVersionLock.LockPatch;
                        isApproximated = false;
                    }
                }
                return(new ParseResult(new SVersionBound(r.Version, defaultBound, quality), isApproximated, r.FourtPartLost), false);
            }
            if (r.FMajor < 0)
            {
                return(new ParseResult(new SVersionBound(_000Version, SVersionLock.None, quality), isApproximated: !includePrerelease, false), false);
            }
            if (r.FMinor < 0)
            {
                return(new ParseResult(new SVersionBound(SVersion.Create(r.FMajor, 0, 0), SVersionLock.LockMajor, quality), isApproximated: !includePrerelease, false), true);
            }
            return(new ParseResult(new SVersionBound(SVersion.Create(r.FMajor, r.FMinor, 0), SVersionLock.LockMinor, quality), isApproximated: !includePrerelease, false), false);
        }
Пример #11
0
 /// <summary>
 /// Protected copy constructor with <see cref="BuildMetaData"/>.
 /// </summary>
 /// <param name="other">Origin version.</param>
 /// <param name="buildMetaData">New BuildMetaData. Must not be null.</param>
 /// <param name="csVersion">Companion CSVersion.</param>
 protected SVersion(SVersion other, string buildMetaData, CSVersion?csVersion)
 {
     if (other == null)
     {
         throw new ArgumentNullException(nameof(other));
     }
     if (!other.IsValid)
     {
         throw new InvalidOperationException("Version must be valid.");
     }
     _csVersion     = csVersion ?? (this as CSVersion);
     Major          = other.Major;
     Minor          = other.Minor;
     Patch          = other.Patch;
     Prerelease     = other.Prerelease;
     BuildMetaData  = buildMetaData ?? throw new ArgumentNullException(nameof(buildMetaData));
     NormalizedText = ComputeNormalizedText(Major, Minor, Patch, Prerelease, buildMetaData);
 }
Пример #12
0
        static void InlineAssertInvariants(CSVersion v)
        {
#if DEBUG
            if (!_alreadyInCheck && v.IsValid)
            {
                _alreadyInCheck = true;
                try
                {
                    //// Systematically checks that a valid CSVersion can be parsed back in Long or Short form.
                    Debug.Assert(SVersion.TryParse(v.ToString(CSVersionFormat.Normalized)).Equals(v));
                    Debug.Assert(SVersion.TryParse(v.ToString(CSVersionFormat.ShortForm)).Equals(v));
                }
                finally
                {
                    _alreadyInCheck = false;
                }
            }
#endif
        }
Пример #13
0
        int CompareValid(SVersion other)
        {
            var r = Major - other.Major;

            if (r != 0)
            {
                return(r);
            }

            r = Minor - other.Minor;
            if (r != 0)
            {
                return(r);
            }

            r = Patch - other.Patch;
            if (r != 0)
            {
                return(r);
            }

            return(ComparePreRelease(Prerelease, other.Prerelease));
        }
Пример #14
0
        static SVersion TryParseVersion(ref ReadOnlySpan <char> s, out bool fourthPartLost)
        {
            Debug.Assert(s.Length > 0);
            fourthPartLost = false;
            var v = SVersion.TryParse(ref s);

            if (v.IsValid)
            {
                // If only the 3 first parts have been read...
                fourthPartLost = SkipExtraPartsAndPrereleaseIfAny(ref s);
            }
            else
            {
                if (TryMatchNonNegativeInt(ref s, out int major))
                {
                    if (s.Length == 0 || !TryMatch(ref s, '.'))
                    {
                        return(SVersion.Create(major, 0, 0));
                    }
                    if (!TryMatchNonNegativeInt(ref s, out int minor))
                    {
                        return(new SVersion("Expected Nuget minor part.", null));
                    }
                    // Try to save the fourth part: in such case the patch is read.
                    int patch = 0;
                    if (s.Length > 0 && TryMatch(ref s, '.') &&
                        s.Length > 0 && TryMatchNonNegativeInt(ref s, out patch) &&
                        s.Length > 0 && TryMatch(ref s, '.') &&
                        s.Length > 0 && TryMatchNonNegativeInt(ref s, out int _))
                    {
                        fourthPartLost = true;
                    }
                    return(SVersion.Create(major, minor, patch));
                }
            }
            return(v);
        }
Пример #15
0
        /// <summary>
        /// Tries to parse a version bound: it is a <see cref="SVersion.TryParse(ref ReadOnlySpan{char}, bool, bool, bool)"/> that may be
        /// followed by an optional bracketed "[<see cref="TryParseLockAndMinQuality"/>]".
        /// The head is forwarded right after the match: on success, the head may be on any kind of character.
        /// </summary>
        /// <param name="head">The string to parse (leading and internal white spaces between tokens are skipped).</param>
        /// <param name="bound">The result. This is <see cref="SVersionBound.None"/> on error.</param>
        /// <param name="defaultLock">Default lock to apply if none is provided.</param>
        /// <param name="defaultQuality">Default quality to apply if none is provided.</param>
        /// <returns>True on success, false otherwise.</returns>
        public static bool TryParse(ref ReadOnlySpan <char> head, out SVersionBound bound, SVersionLock defaultLock = SVersionLock.None, PackageQuality defaultQuality = PackageQuality.None)
        {
            var sHead = head;

            bound = SVersionBound.None;
            var v = SVersion.TryParse(ref Trim(ref head), checkBuildMetaDataSyntax: false);

            if (!v.IsValid)
            {
                head = sHead;
                return(false);
            }
            SVersionLock   l = SVersionLock.None;
            PackageQuality q = PackageQuality.None;

            if (Trim(ref head).Length > 0 && TryMatch(ref head, '['))
            {
                // Allows empty []. Note that TryParseLockAndMinQuality calls Trim.
                TryParseLockAndMinQuality(ref head, out l, out q);
                // Match the closing ] if it's here. Ignores it if it's not here.
                if (Trim(ref head).Length > 0)
                {
                    TryMatch(ref head, ']');
                }
            }
            if (l == SVersionLock.None)
            {
                l = defaultLock;
            }
            if (q == PackageQuality.None)
            {
                q = defaultQuality;
            }
            bound = new SVersionBound(v, l, q);
            return(true);
        }
Пример #16
0
 /// <summary>
 /// Standard TryParse pattern that returns a boolean rather than the resulting <see cref="SVersion"/>.
 /// See <see cref="TryParse(string,bool,bool)"/>.
 /// </summary>
 /// <param name="s">String to parse.</param>
 /// <param name="v">Resulting version.</param>
 /// <param name="handleCSVersion">
 /// False to skip <see cref="CSVersion"/> conformance lookup. The resulting version
 /// will be a <see cref="SVersion"/> even if it is a valid <see cref="CSVersion"/>.
 /// This should be used in rare scenario where the normalization of a <see cref="CSVersion"/> (standardization
 /// of prerelease names) must not be done.
 /// </param>
 /// <param name="checkBuildMetaDataSyntax">False to opt-out of strict <see cref="BuildMetaData"/> compliance.</param>
 /// <returns>True on success, false otherwise.</returns>
 public static bool TryParse(string s, out SVersion v, bool handleCSVersion = true, bool checkBuildMetaDataSyntax = true)
 {
     v = TryParse(s, handleCSVersion, checkBuildMetaDataSyntax);
     return(v.IsValid);
 }
Пример #17
0
 /// <summary>
 /// Gets the standard Informational version string.
 /// If <see cref="SVersion.IsValid"/> is false this throws an <see cref="InvalidOperationException"/>:
 /// the constant <see cref="InformationalVersion.ZeroInformationalVersion"/> should be used when IsValid is false.
 /// </summary>
 /// <param name="commitSha">The SHA1 of the commit (must be 40 hex digits).</param>
 /// <param name="commitDateUtc">The commit date (must be in UTC).</param>
 /// <param name="buildInfo">Can be null: not null for post-release version.</param>
 /// <returns>The informational version.</returns>
 public string GetInformationalVersion(string commitSha, DateTime commitDateUtc, CIBuildDescriptor buildInfo)
 {
     return(IsValid && buildInfo != null
             ? SVersion.Parse(ToString( CSVersionFormat.Normalized, buildInfo )).GetInformationalVersion(commitSha, commitDateUtc)
             : GetInformationalVersion(commitSha, commitDateUtc));
 }