/// <summary> /// Initializes a new version range on a valid <see cref="Base"/> version. /// </summary> /// <param name="version">The base version that must be valid.</param> /// <param name="r">The lock to apply.</param> /// <param name="minQuality">The minimal quality to accept.</param> public SVersionBound(SVersion?version = null, SVersionLock r = SVersionLock.None, PackageQuality minQuality = PackageQuality.None) { _base = version ?? SVersion.ZeroVersion; if (!_base.IsValid) { throw new ArgumentException("Must be valid. Error: " + _base.ErrorMessage, nameof(version)); } if (minQuality == PackageQuality.Stable && r == SVersionLock.LockPatch) { r = SVersionLock.Lock; } _minQuality = minQuality; Lock = r; }
/// <summary> /// Tries to parse one of the <see cref="SVersionLock"/> terms (the <paramref name="head"/> must be at the start, no trimming is done). /// Note that match is case insensitive and that all "Lock" wan be written as "Locked". /// The <paramref name="head"/> is forwarded right after the match: the head may be on any kind of character. /// </summary> /// <param name="head">The string to parse.</param> /// <param name="l">The read lock.</param> /// <returns>True on success, false otherwise.</returns> public static bool TryMatch(ref ReadOnlySpan <char> head, out SVersionLock l) { l = SVersionLock.None; if (head.Length == 0) { return(false); } if (!head.StartsWith(nameof(SVersionLock.Lock), StringComparison.OrdinalIgnoreCase)) { if (head.StartsWith(nameof(SVersionLock.None), StringComparison.OrdinalIgnoreCase)) { head = head.Slice(4); return(true); } return(false); } head = head.Slice(4); if (head.StartsWith("ed", StringComparison.OrdinalIgnoreCase)) { head = head.Slice(2); } if (head.StartsWith("major", StringComparison.OrdinalIgnoreCase)) { head = head.Slice(5); l = SVersionLock.LockMajor; return(true); } if (head.StartsWith("minor", StringComparison.OrdinalIgnoreCase)) { head = head.Slice(5); l = SVersionLock.LockMinor; return(true); } if (head.StartsWith("patch", StringComparison.OrdinalIgnoreCase)) { head = head.Slice(5); l = SVersionLock.LockPatch; return(true); } l = SVersionLock.Lock; return(true); }
/// <summary> /// Tries to parse "<see cref="SVersionLock"/>[,<see cref="PackageQuality"/>]" or "<see cref="PackageQuality"/>[,<see cref="SVersionLock"/>]". /// Note that match is case insensitive, that white spaces are silently ignored, that all "Lock" wan be written as "Locked" and that "rc" is /// a synonym of <see cref="PackageQuality.ReleaseCandidate"/>. /// The <paramref name="head"/> is forwarded right after the match: the head may be on any kind of character. /// </summary> /// <param name="head">The string to parse (leading and internal white spaces are skipped).</param> /// <param name="l">The read lock.</param> /// <param name="q">The read quality.</param> /// <returns>True on success, false otherwise.</returns> public static bool TryParseLockAndMinQuality(ref ReadOnlySpan <char> head, out SVersionLock l, out PackageQuality q) { var sSaved = head; l = SVersionLock.None; q = PackageQuality.None; if (head.Length == 0) { return(false); } if (SVersionLockExtension.TryMatch(ref Trim(ref head), out l)) { if (TryMatch(ref Trim(ref head), ',')) { // This handles None,None: if the first read is "None", then it could have been the "None" of the quality: // if we don't match a Quality we give a second chance to the Lock. if (!PackageQualityExtension.TryMatch(ref Trim(ref head), out q) && l == SVersionLock.None) { SVersionLockExtension.TryMatch(ref Trim(ref head), out l); } return(true); } return(true); } if (PackageQualityExtension.TryMatch(ref Trim(ref head), out q)) { if (TryMatch(ref Trim(ref head), ',')) { SVersionLockExtension.TryMatch(ref Trim(ref head), out l); return(true); } return(true); } head = sSaved; return(false); }
/// <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); }
/// <summary> /// Sets a lock by returning this or a new <see cref="SVersionBound"/>. /// </summary> /// <param name="r">The lock to set.</param> /// <returns>This or a new range.</returns> public SVersionBound SetLock(SVersionLock r) => r != Lock ? new SVersionBound(Base, r, MinQuality) : this;
/// <summary> /// Tries to parse one of the <see cref="SVersionLock"/> terms (the <paramref name="head"/> must be at the start, no trimming is done). /// Note that match is case insensitive and that all "Lock" wan be written as "Locked". /// </summary> /// <param name="head">The string to parse.</param> /// <param name="l">The read lock.</param> /// <returns>True on success, false otherwise.</returns> public static bool TryMatch(ReadOnlySpan <char> head, out SVersionLock l) => TryMatch(ref head, out l);
/// <summary> /// Intersects this lock with another one: the strongest wins, merging <see cref="SVersionLock.LockMinor"/> with <see cref="SVersionLock.Lock"/> /// results in <see cref="SVersionLock.Lock"/>. /// </summary> /// <param name="this">This lock.</param> /// <param name="other">The other lock.</param> /// <returns>The strongest of the two.</returns> public static SVersionLock Intersect(this SVersionLock @this, SVersionLock other) { return(@this > other ? @this : other); }
/// <summary> /// Merges this lock with another one: the weakest wins, merging <see cref="SVersionLock.LockMinor"/> with <see cref="SVersionLock.Lock"/> /// results in <see cref="SVersionLock.LockMinor"/>. /// </summary> /// <param name="this">This lock.</param> /// <param name="other">The other lock.</param> /// <returns>The weakest of the two.</returns> public static SVersionLock Union(this SVersionLock @this, SVersionLock other) { return(@this < other ? @this : other); }
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); }
/// <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"/>]". /// </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">The <see cref="SVersionLock"/> to use when no lock appears in the string to parse.</param> /// <param name="defaultQuality">The <see cref="PackageQuality"/> to use when no lock appears in the string to parse.</param> /// <returns>True on success, false otherwise.</returns> public static bool TryParse(ReadOnlySpan <char> head, out SVersionBound bound, SVersionLock defaultLock = SVersionLock.None, PackageQuality defaultQuality = PackageQuality.None) => TryParse(ref head, out bound, defaultLock, defaultQuality);
public void parse_SVersionBound_with_defaults(string p, string expected, SVersionLock defL, PackageQuality defQ) { SVersionBound.TryParse(p, out var b, defL, defQ).Should().BeTrue(); b.ToString().Should().Be(expected); }