/// <summary> /// Initializes a new filter from a string. /// Throws an <see cref="ArgumentException"/> on invalid syntax. /// Simply uses <see cref="TryParse(ReadOnlySpan{char}, out PackageQualityFilter)"/>) to handle invalid syntax. /// </summary> /// <param name="s">The string.</param> public PackageQualityFilter(ReadOnlySpan <char> s) { if (!TryParse(s, out PackageQualityFilter p)) { throw new ArgumentException("Invalid PackageQualityFilter syntax."); } _min = p._min; _max = p._max; }
static bool TryParse(string s, out PackageQuality q, PackageQuality def) { q = def; if (s.Length == 0) { return(true); } return(Enum.TryParse(s, out q)); }
/// <summary> /// Initializes a new filter. Min must be lower or equal to max otherwise an <see cref="ArgumentException"/> is thrown. /// </summary> /// <param name="min">The minimal quality.</param> /// <param name="max">The maximal quality.</param> public PackageQualityFilter(PackageQuality min, PackageQuality max) { if (min > max) { throw new ArgumentException("min must be lower or equal to max."); } _min = min; _max = max; }
/// <summary> /// Gets the best version for a given quality or null if no such version exists. /// </summary> /// <param name="quality">The minimal required quality.</param> /// <returns>The best version or null if not found.</returns> public SVersion?GetVersion(PackageQuality quality) { return(quality switch { PackageQuality.Stable => Stable, PackageQuality.ReleaseCandidate => ReleaseCandidate, PackageQuality.Preview => Preview, PackageQuality.Exploratory => Exploratory, _ => CI, });
/// <summary> /// Initializes a new filter (min and max are reordered if needed). /// </summary> /// <param name="min">The minimal quality.</param> /// <param name="max">The maximal quality.</param> public PackageQualityFilter(PackageQuality min, PackageQuality max) { if (min > max) { Min = max; Max = min; } else { Min = min; Max = max; } }
/// <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> /// Initializes a new filter by parsing a nullable string. /// Throws an <see cref="ArgumentException"/> on invalid syntax: use <see cref="TryParse(string, out PackageQualityFilter)"/> /// to handle invalid syntax. /// </summary> /// <param name="s">The string. Can be null or empty.</param> public PackageQualityFilter(string s) { if (!String.IsNullOrWhiteSpace(s)) { if (!TryParse(s, out PackageQualityFilter p)) { throw new ArgumentException("Invalid PackageQualityFilter syntax."); } Min = p.Min; Max = p.Max; } else { Min = Max = PackageQuality.None; } }
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> /// Attempts to parse a string as a <see cref="PackageQualityFilter"/>. /// Note that the parse is case insensitive, that white spaces are silently ignored and min/max can be reversed. /// The <paramref name="head"/> is forwarded right after the match: the head may be on any kind of character. /// <para> /// Examples: /// "Stable" (is the same as "Stable-Stable"): only <see cref="PackageQuality.Stable"/> is accepted /// "CI-Stable" (is the same as "-Stable" or "CI-" or ""): everything is accepted. /// "-ReleaseCandidate" (same as "CI-RC" or "CI-ReleaseCandidate"): everything except Stable. /// "Exploratory-Preview": No CI, ReleaseCandidate, nor Stable. /// </para> /// </summary> /// <param name="head">The string to parse (leading and internal white spaces between tokens are skipped).</param> /// <param name="filter">The result.</param> /// <returns>True on success, false on error.</returns> public static bool TryParse(ref ReadOnlySpan <char> head, out PackageQualityFilter filter) { var start = head; head = head.TrimStart(); bool hasMin = PackageQualityExtension.TryMatch(ref head, out var min); bool hasMax = false; PackageQuality max = PackageQuality.Stable; var sHead = head; if ((head = head.TrimStart()).Length > 0 && head[0] == '-') { if (!hasMin) { sHead = head; } head = head.Slice(1).TrimStart(); hasMax = PackageQualityExtension.TryMatch(ref head, out max); if (!hasMax) { head = sHead; } } else { head = sHead; } if (hasMin || hasMax) { if (max == PackageQuality.None) { max = PackageQuality.Stable; } if (min > max) { var t = max; max = min; min = t; } filter = new PackageQualityFilter(min, max == PackageQuality.None ? PackageQuality.Stable : max); return(true); } filter = new PackageQualityFilter(); head = start; return(false); }
/// <summary> /// Gets the best version for a given quality or null if no such version exists. /// </summary> /// <param name="quality">The minimal required quality.</param> /// <returns>The best version or null if not found.</returns> public SVersion GetVersion(PackageQuality quality) { if (!IsValid) { throw new InvalidOperationException(); } switch (quality) { case PackageQuality.Release: return(Stable); case PackageQuality.ReleaseCandidate: return(Latest); case PackageQuality.Preview: return(Preview); case PackageQuality.Exploratory: return(Exploratory); default: return(CI); } }
/// <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> /// Gets the standard package labels that corresponds to this <see cref="PackageQuality"/>. /// </summary> /// <param name="this">This PackageQuality.</param> /// <returns>The corresponding labels.</returns> public static IReadOnlyList <PackageLabel> GetLabels(this PackageQuality @this) { return(_map[(int)@this]); }
public void version_to_quality_mapping(string version, PackageQuality q) { SVersion.TryParse(version).PackageQuality.Should().Be(q); }
/// <summary> /// Gets this quality followed by all its lowest qualities. /// </summary> /// <param name="this">This quality.</param> /// <returns>This quality followed by its lowest ones.</returns> public static IReadOnlyList <PackageQuality> GetAllQualities(this PackageQuality @this) => _map[@this switch
/// <summary> /// Intersects this quality with another one: the strongest wins, merging <see cref="PackageQuality.CI"/> and <see cref="PackageQuality.Stable"/> /// results in <see cref="PackageQuality.Stable"/>. /// </summary> /// <param name="this">This quality.</param> /// <param name="other">The other quality.</param> /// <returns>The strongest of the two.</returns> public static PackageQuality Intersect(this PackageQuality @this, PackageQuality other) { return(@this > other ? @this : other); }
/// <summary> /// Merges this quality with another one: the weakest wins, merging <see cref="PackageQuality.CI"/> and <see cref="PackageQuality.Stable"/> /// results in <see cref="PackageQuality.CI"/>. /// </summary> /// <param name="this">This quality.</param> /// <param name="other">The other quality.</param> /// <returns>The weakest of the two.</returns> public static PackageQuality Union(this PackageQuality @this, PackageQuality other) { return(@this < other ? @this : other); }
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); }
public void Quality_to_Labels_mappings(PackageQuality q, string labels) { var l = labels.Split(',').Select(s => (PackageLabel)Enum.Parse(typeof(PackageLabel), s)); q.GetLabels().Should().BeEquivalentTo(l, o => o.WithStrictOrdering()); }
/// <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);
/// <summary> /// Sets a lock by returning this or a new <see cref="SVersionBound"/>. /// </summary> /// <param name="min">The lock to set.</param> /// <returns>This or a new range.</returns> public SVersionBound SetMinQuality(PackageQuality min) => MinQuality != min ? new SVersionBound(Base, Lock, min) : this;
/// <summary> /// Gets whether this filter allows the specified quality. /// </summary> /// <param name="q">The quality to challenge. <see cref="PackageQuality.None"/> is never accepted.</param> /// <returns>Whether <paramref name="q"/> is accepted or not.</returns> public bool Accepts(PackageQuality q) => q != PackageQuality.None && (!HasMin || q >= Min) && (!HasMax || q <= Max);
/// <summary> /// This method must choose between possible versions. It may return null to cancel the process. /// </summary> /// <param name="m">The monitor to use.</param> /// <param name="c">The context.</param> public void ChooseFinalVersion(IActivityMonitor m, IReleaseVersionSelectorContext c) { Console.WriteLine("========="); Console.Write($"======== {c.Solution.Solution.Name} "); if (c.PreviousVersionCommitSha != null) { Console.Write($" last release: {c.PreviousVersion}"); var diffResult = c.GetProjectsDiff(m); if (diffResult == null) { c.Cancel(); return; } if (diffResult.Diffs.All(d => d.DiffType == DiffRootResultType.None) && diffResult.Others.DiffType == DiffRootResultType.None) { Console.WriteLine($" (No change in {c.Solution.Solution.GeneratedArtifacts.Select( p => p.Artifact.Name ).Concatenate()})"); } else { Console.WriteLine(", changes:"); } Console.WriteLine(diffResult.ToString()); } else { Console.WriteLine("(No previous released version)"); } var projExRefNotRelease = c.Solution.Solution.Projects .Where(p => p.IsPublished && p.PackageReferences.Any(q => q.Kind == ArtifactDependencyKind.Transitive)) .Select(p => { List <PackageReference> pcks = p.PackageReferences .Where(q => q.Kind == ArtifactDependencyKind.Transitive) .Where( q => !c.Solution.ImportedLocalPackages .Any(s => s.Package.Artifact == q.Target.Artifact) ) .ToList(); if (pcks.Any()) { PackageQuality worstQuality = pcks.Select(q => q.Target.Version.PackageQuality).Min(); return(p, pcks.Where(q => q.Target.Version.PackageQuality == worstQuality)); } p = null; return(p, Array.Empty <PackageReference>()); }) //There should be at least one package reference .Where(x => x.p != null) .GroupBy(p => p.Item2.First().Target.Version.PackageQuality).ToList(); //ugliest LINQ i ever wrote, should take 3 lines. var min = projExRefNotRelease.Any() ? projExRefNotRelease.Min(q => q.Key) : PackageQuality.None; var worst = min != PackageQuality.None ? projExRefNotRelease.SingleOrDefault(p => p.Key == min) : null; if (worst == null || worst.Key == PackageQuality.Release) { Console.WriteLine("Nothing prevent to choose the Release quality."); } else { var prev = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Best quality is {worst.Key} because of projects:"); foreach (var proj in worst) { Console.WriteLine(" => " + proj.p.Name + " caused by packages references " + string.Join(", ", proj.Item2.Select(p => p.ToString()).ToArray())); } Console.ForegroundColor = prev; } foreach (var kv in c.PossibleVersions) { Console.Write($"= {(int)kv.Key} - {kv.Key} => "); for (int i = 0; i < kv.Value.Count; i++) { CSVersion version = kv.Value[i]; var prev = Console.ForegroundColor; Console.ForegroundColor = kv.Key != ReleaseLevel.None ? version.PackageQuality > (worst?.Key ?? PackageQuality.Release) ? ConsoleColor.Red : ConsoleColor.Green : ConsoleColor.White; Console.Write(version.NormalizedText); if (i < kv.Value.Count - 1) { Console.Write(", "); } Console.ForegroundColor = prev; } Console.WriteLine(); } if (c.CanUsePreviouslyResolvedInfo) { Console.WriteLine($"= A - Already selected Level and Version: {c.PreviouslyResolvedInfo.Level} - {c.PreviouslyResolvedInfo.Version}"); } Console.WriteLine("= X - Cancel."); ReleaseLevel level = ReleaseLevel.None; char a; do { while ("0123AX".IndexOf((a = Console.ReadKey().KeyChar)) < 0) { Console.Write('\b'); } if (a == 'X') { c.Cancel(); } if (a == 'A') { c.SetChoice(c.PreviouslyResolvedInfo.Level, c.PreviouslyResolvedInfo.Version); } else { level = (ReleaseLevel)(a - '0'); } }while(!c.IsAnswered && c.PossibleVersions[level].Count == 0); if (!c.IsAnswered) { Console.WriteLine($"= Selected: {level}, now choose a version:"); var possibleVersions = c.PossibleVersions[level]; for (int i = 0; i < possibleVersions.Count; ++i) { Console.WriteLine($"= {i} - {possibleVersions[i]}"); } Console.WriteLine($"= X - Cancel."); while (!c.IsAnswered) { Console.Write($"= (Enter the final release number and press enter)> "); string line = Console.ReadLine(); if (line == "X") { c.Cancel(); } else if (Int32.TryParse(line, out var num) && num >= 0 && num < possibleVersions.Count) { c.SetChoice(level, possibleVersions[num]); } Console.WriteLine(); } } }