Skip to content

Commit

Permalink
Add an includePrerelease option for Range.IsSatisfied and related met…
Browse files Browse the repository at this point in the history
…hods (#51)
  • Loading branch information
adamreeve authored May 13, 2021
1 parent 06f1afe commit 713d3b7
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 77 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Examples:
Pre-Release Versions
--------------------

Versions with a pre-release can only satisfy ranges that contain
Versions with a pre-release can normally only satisfy ranges that contain
a comparator with a pre-release version, and the comparator
version's major, minor and patch components must match those of the
version being tested.
Expand All @@ -148,6 +148,20 @@ var range2 = new Range(">=1.2.3");
range2.IsSatisfied("1.2.4-alpha"); // false
```

To change this behaviour and allow any pre-release version to satisfy a range,
you can set the `includePrerelease` argument to true:

```C#
var range = new Range(">=1.2.3-beta.2");
range.IsSatisfied("1.2.4-beta.5", includePrerelease=true); // true
var range2 = new Range(">=1.2.3");
range2.IsSatisfied("1.2.4-alpha", includePrerelease=true); // true
```

The `Range.Satisfying` and `Range.MaxSatisfying` methods similarly support
an `includePrerelease` argument to allow any pre-release version.

Version Comparisons
-------------------

Expand Down
51 changes: 35 additions & 16 deletions src/SemanticVersioning/Comparator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public Comparator(string input)
}
break;
case Operator.GreaterThan:
ComparatorType = Operator.GreaterThanOrEqual;
ComparatorType = Operator.GreaterThanOrEqualIncludingPrereleases;
if (!partialVersion.Major.HasValue)
{
// >* is unsatisfiable, so use <0.0.0
Expand All @@ -76,9 +76,17 @@ public Comparator(string input)
Version = new Version(partialVersion.Major.Value, partialVersion.Minor.Value + 1, 0);
}
break;
case Operator.LessThan:
// <1.2.x means <1.2.0 but not allowing 1.2.0 prereleases if includePrereleases is used
ComparatorType = Operator.LessThanExcludingPrereleases;
Version = partialVersion.ToZeroVersion();
break;
case Operator.GreaterThanOrEqual:
// >=1.2.x means >=1.2.0 and includes 1.2.0 prereleases if includePrereleases is used
ComparatorType = Operator.GreaterThanOrEqualIncludingPrereleases;
Version = partialVersion.ToZeroVersion();
break;
default:
// <1.2.x means <1.2.0
// >=1.2.x means >=1.2.0
Version = partialVersion.ToZeroVersion();
break;
}
Expand Down Expand Up @@ -137,16 +145,20 @@ public bool IsSatisfied(Version version)
{
switch(ComparatorType)
{
case(Operator.Equal):
case Operator.Equal:
return version == Version;
case(Operator.LessThan):
case Operator.LessThan:
return version < Version;
case(Operator.LessThanOrEqual):
case Operator.LessThanOrEqual:
return version <= Version;
case(Operator.GreaterThan):
case Operator.GreaterThan:
return version > Version;
case(Operator.GreaterThanOrEqual):
case Operator.GreaterThanOrEqual:
return version >= Version;
case Operator.GreaterThanOrEqualIncludingPrereleases:
return version >= Version || (version.IsPreRelease && version.BaseVersion() == Version);
case Operator.LessThanExcludingPrereleases:
return version < Version && !(version.IsPreRelease && version.BaseVersion() == Version);
default:
throw new InvalidOperationException("Comparator type not recognised.");
}
Expand All @@ -156,12 +168,15 @@ public bool Intersects(Comparator other)
{
Func<Comparator, bool> operatorIsGreaterThan = c =>
c.ComparatorType == Operator.GreaterThan ||
c.ComparatorType == Operator.GreaterThanOrEqual;
c.ComparatorType == Operator.GreaterThanOrEqual ||
c.ComparatorType == Operator.GreaterThanOrEqualIncludingPrereleases;
Func<Comparator, bool> operatorIsLessThan = c =>
c.ComparatorType == Operator.LessThan ||
c.ComparatorType == Operator.LessThanOrEqual;
c.ComparatorType == Operator.LessThanOrEqual ||
c.ComparatorType == Operator.LessThanExcludingPrereleases;
Func<Comparator, bool> operatorIncludesEqual = c =>
c.ComparatorType == Operator.GreaterThanOrEqual ||
c.ComparatorType == Operator.GreaterThanOrEqualIncludingPrereleases ||
c.ComparatorType == Operator.Equal ||
c.ComparatorType == Operator.LessThanOrEqual;

Expand All @@ -188,26 +203,30 @@ public enum Operator
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
GreaterThanOrEqualIncludingPrereleases,
LessThanExcludingPrereleases,
}

public override string ToString()
{
string operatorString = null;
switch(ComparatorType)
{
case(Operator.Equal):
case Operator.Equal:
operatorString = "=";
break;
case(Operator.LessThan):
case Operator.LessThan:
case Operator.LessThanExcludingPrereleases:
operatorString = "<";
break;
case(Operator.LessThanOrEqual):
case Operator.LessThanOrEqual:
operatorString = "<=";
break;
case(Operator.GreaterThan):
case Operator.GreaterThan:
operatorString = ">";
break;
case(Operator.GreaterThanOrEqual):
case Operator.GreaterThanOrEqual:
case Operator.GreaterThanOrEqualIncludingPrereleases:
operatorString = ">=";
break;
default:
Expand All @@ -232,7 +251,7 @@ public override bool Equals(object other)

public override int GetHashCode()
{
return ToString().GetHashCode();
return new { ComparatorType, Version }.GetHashCode();
}
}
}
10 changes: 6 additions & 4 deletions src/SemanticVersioning/ComparatorSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ private ComparatorSet(IEnumerable<Comparator> comparators)
_comparators = comparators.ToList();
}

public bool IsSatisfied(Version version)
public bool IsSatisfied(Version version, bool includePrerelease = false)
{
bool satisfied = _comparators.All(c => c.IsSatisfied(version));
if (version.PreRelease != null)
if (version.PreRelease != null && !includePrerelease)
{
// If the version is a pre-release, then one of the
// comparators must have the same version and also include
Expand All @@ -86,10 +86,12 @@ public ComparatorSet Intersect(ComparatorSet other)
{
Func<Comparator, bool> operatorIsGreaterThan = c =>
c.ComparatorType == Comparator.Operator.GreaterThan ||
c.ComparatorType == Comparator.Operator.GreaterThanOrEqual;
c.ComparatorType == Comparator.Operator.GreaterThanOrEqual ||
c.ComparatorType == Comparator.Operator.GreaterThanOrEqualIncludingPrereleases;
Func<Comparator, bool> operatorIsLessThan = c =>
c.ComparatorType == Comparator.Operator.LessThan ||
c.ComparatorType == Comparator.Operator.LessThanOrEqual;
c.ComparatorType == Comparator.Operator.LessThanOrEqual ||
c.ComparatorType == Comparator.Operator.LessThanExcludingPrereleases;
var maxOfMins =
_comparators.Concat(other._comparators)
.Where(operatorIsGreaterThan)
Expand Down
25 changes: 13 additions & 12 deletions src/SemanticVersioning/Desugarer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static Tuple<int, Comparator[]> TildeRange(string spec)

return Tuple.Create(
match.Length,
minMaxComparators(minVersion, maxVersion));
MinMaxComparators(minVersion, maxVersion));
}

// Allows changes that do not modify the left-most non-zero digit
Expand Down Expand Up @@ -95,7 +95,7 @@ public static Tuple<int, Comparator[]> CaretRange(string spec)

return Tuple.Create(
match.Length,
minMaxComparators(minVersion, maxVersion));
MinMaxComparators(minVersion, maxVersion, minOperator: Comparator.Operator.GreaterThanOrEqualIncludingPrereleases));
}

public static Tuple<int, Comparator[]> HyphenRange(string spec)
Expand Down Expand Up @@ -128,7 +128,7 @@ public static Tuple<int, Comparator[]> HyphenRange(string spec)
var minVersion = minPartialVersion.ToZeroVersion();

Comparator.Operator maxOperator = maxPartialVersion.IsFull()
? Comparator.Operator.LessThanOrEqual : Comparator.Operator.LessThan;
? Comparator.Operator.LessThanOrEqual : Comparator.Operator.LessThanExcludingPrereleases;

Version maxVersion = null;

Expand All @@ -153,7 +153,10 @@ public static Tuple<int, Comparator[]> HyphenRange(string spec)
}
return Tuple.Create(
match.Length,
minMaxComparators(minVersion, maxVersion, maxOperator));
MinMaxComparators(
minVersion, maxVersion,
minOperator: Comparator.Operator.GreaterThanOrEqualIncludingPrereleases,
maxOperator: maxOperator));
}

public static Tuple<int, Comparator[]> StarRange(string spec)
Expand Down Expand Up @@ -206,23 +209,21 @@ public static Tuple<int, Comparator[]> StarRange(string spec)

return Tuple.Create(
match.Length,
minMaxComparators(minVersion, maxVersion));
MinMaxComparators(minVersion, maxVersion));
}

private static Comparator[] minMaxComparators(Version minVersion, Version maxVersion,
Comparator.Operator maxOperator=Comparator.Operator.LessThan)
private static Comparator[] MinMaxComparators(Version minVersion, Version maxVersion,
Comparator.Operator minOperator=Comparator.Operator.GreaterThanOrEqual,
Comparator.Operator maxOperator=Comparator.Operator.LessThanExcludingPrereleases)
{
var minComparator = new Comparator(
Comparator.Operator.GreaterThanOrEqual,
minVersion);
var minComparator = new Comparator(minOperator, minVersion);
if (maxVersion == null)
{
return new [] { minComparator };
}
else
{
var maxComparator = new Comparator(
maxOperator, maxVersion);
var maxComparator = new Comparator(maxOperator, maxVersion);
return new [] { minComparator, maxComparator };
}
}
Expand Down
44 changes: 26 additions & 18 deletions src/SemanticVersioning/Range.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ private Range(IEnumerable<ComparatorSet> comparatorSets)
/// Determine whether the given version satisfies this range.
/// </summary>
/// <param name="version">The version to check.</param>
/// <param name="includePrerelease">When true, allow prerelease versions to satisfy the range.</param>
/// <returns>true if the range is satisfied by the version.</returns>
public bool IsSatisfied(Version version)
public bool IsSatisfied(Version version, bool includePrerelease = false)
{
return _comparatorSets.Any(s => s.IsSatisfied(version));
return _comparatorSets.Any(s => s.IsSatisfied(version, includePrerelease: includePrerelease));
}

/// <summary>
Expand All @@ -48,13 +49,14 @@ public bool IsSatisfied(Version version)
/// </summary>
/// <param name="versionString">The version to check.</param>
/// <param name="loose">When true, be more forgiving of some invalid version specifications.</param>
/// <param name="includePrerelease">When true, allow prerelease versions to satisfy the range.</param>
/// <returns>true if the range is satisfied by the version.</returns>
public bool IsSatisfied(string versionString, bool loose = false)
public bool IsSatisfied(string versionString, bool loose = false, bool includePrerelease = false)
{
try
{
var version = new Version(versionString, loose);
return IsSatisfied(version);
return IsSatisfied(version, includePrerelease: includePrerelease);
}
catch (ArgumentException)
{
Expand All @@ -66,10 +68,11 @@ public bool IsSatisfied(string versionString, bool loose = false)
/// Return the set of versions that satisfy this range.
/// </summary>
/// <param name="versions">The versions to check.</param>
/// <param name="includePrerelease">When true, allow prerelease versions to satisfy the range.</param>
/// <returns>An IEnumerable of satisfying versions.</returns>
public IEnumerable<Version> Satisfying(IEnumerable<Version> versions)
public IEnumerable<Version> Satisfying(IEnumerable<Version> versions, bool includePrerelease = false)
{
return versions.Where(IsSatisfied);
return versions.Where(v => IsSatisfied(v, includePrerelease: includePrerelease));
}

/// <summary>
Expand All @@ -78,32 +81,35 @@ public IEnumerable<Version> Satisfying(IEnumerable<Version> versions)
/// </summary>
/// <param name="versions">The version strings to check.</param>
/// <param name="loose">When true, be more forgiving of some invalid version specifications.</param>
/// <param name="includePrerelease">When true, allow prerelease versions to satisfy the range.</param>
/// <returns>An IEnumerable of satisfying version strings.</returns>
public IEnumerable<string> Satisfying(IEnumerable<string> versions, bool loose = false)
public IEnumerable<string> Satisfying(IEnumerable<string> versions, bool loose = false, bool includePrerelease = false)
{
return versions.Where(v => IsSatisfied(v, loose));
return versions.Where(v => IsSatisfied(v, loose, includePrerelease));
}

/// <summary>
/// Return the maximum version that satisfies this range.
/// </summary>
/// <param name="versions">The versions to select from.</param>
/// <param name="includePrerelease">When true, allow prerelease versions to satisfy the range.</param>
/// <returns>The maximum satisfying version, or null if no versions satisfied this range.</returns>
public Version MaxSatisfying(IEnumerable<Version> versions)
public Version MaxSatisfying(IEnumerable<Version> versions, bool includePrerelease = false)
{
return Satisfying(versions).Max();
return Satisfying(versions, includePrerelease: includePrerelease).Max();
}

/// <summary>
/// Return the maximum version that satisfies this range.
/// </summary>
/// <param name="versionStrings">The version strings to select from.</param>
/// <param name="loose">When true, be more forgiving of some invalid version specifications.</param>
/// <param name="includePrerelease">When true, allow prerelease versions to satisfy the range.</param>
/// <returns>The maximum satisfying version string, or null if no versions satisfied this range.</returns>
public string MaxSatisfying(IEnumerable<string> versionStrings, bool loose = false)
public string MaxSatisfying(IEnumerable<string> versionStrings, bool loose = false, bool includePrerelease = false)
{
var versions = ValidVersions(versionStrings, loose);
var maxVersion = MaxSatisfying(versions);
var maxVersion = MaxSatisfying(versions, includePrerelease: includePrerelease);
return maxVersion == null ? null : maxVersion.ToString();
}

Expand Down Expand Up @@ -179,11 +185,12 @@ public override int GetHashCode()
/// <param name="rangeSpec">The range specification.</param>
/// <param name="versionString">The version to check.</param>
/// <param name="loose">When true, be more forgiving of some invalid version specifications.</param>
/// <param name="includePrerelease">When true, allow prerelease versions to satisfy the range.</param>
/// <returns>true if the range is satisfied by the version.</returns>
public static bool IsSatisfied(string rangeSpec, string versionString, bool loose = false)
public static bool IsSatisfied(string rangeSpec, string versionString, bool loose = false, bool includePrerelease = false)
{
var range = new Range(rangeSpec);
return range.IsSatisfied(versionString);
return range.IsSatisfied(versionString, loose: loose, includePrerelease: includePrerelease);
}

/// <summary>
Expand All @@ -194,10 +201,10 @@ public static bool IsSatisfied(string rangeSpec, string versionString, bool loos
/// <param name="versions">The version strings to check.</param>
/// <param name="loose">When true, be more forgiving of some invalid version specifications.</param>
/// <returns>An IEnumerable of satisfying version strings.</returns>
public static IEnumerable<string> Satisfying(string rangeSpec, IEnumerable<string> versions, bool loose = false)
public static IEnumerable<string> Satisfying(string rangeSpec, IEnumerable<string> versions, bool loose = false, bool includePrerelease = false)
{
var range = new Range(rangeSpec);
return range.Satisfying(versions);
return range.Satisfying(versions, loose: loose, includePrerelease: includePrerelease);
}

/// <summary>
Expand All @@ -206,11 +213,12 @@ public static IEnumerable<string> Satisfying(string rangeSpec, IEnumerable<strin
/// <param name="rangeSpec">The range specification.</param>
/// <param name="versionStrings">The version strings to select from.</param>
/// <param name="loose">When true, be more forgiving of some invalid version specifications.</param>
/// <param name="includePrerelease">When true, allow prerelease versions to satisfy the range.</param>
/// <returns>The maximum satisfying version string, or null if no versions satisfied this range.</returns>
public static string MaxSatisfying(string rangeSpec, IEnumerable<string> versionStrings, bool loose = false)
public static string MaxSatisfying(string rangeSpec, IEnumerable<string> versionStrings, bool loose = false, bool includePrerelease = false)
{
var range = new Range(rangeSpec);
return range.MaxSatisfying(versionStrings);
return range.MaxSatisfying(versionStrings, includePrerelease: includePrerelease);
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/SemanticVersioning/Version.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public class Version : IComparable<Version>, IComparable, IEquatable<Version>
/// </summary>
public string Build { get { return _build; } }

/// <summary>
/// Whether this version is a pre-release
/// </summary>
public bool IsPreRelease { get { return !string.IsNullOrEmpty(_preRelease); } }

private static Regex strictRegex = new Regex(@"^
\s*v?
([0-9]|[1-9][0-9]+) # major version
Expand Down
Loading

0 comments on commit 713d3b7

Please sign in to comment.