wip
Some checks failed
default / dotnet-default-workflow (push) Failing after 1m40s

This commit is contained in:
Louis Seubert 2026-05-21 20:42:05 +02:00
commit f9fb920e07
Signed by: louis9902
GPG key ID: 4B9DB28F826553BD
5 changed files with 231 additions and 238 deletions

View file

@ -81,6 +81,16 @@ internal sealed class SemanticVersionRangeTests
await Assert.That(r.Contains(v)).IsEqualTo(expected);
}
[Test]
public async Task I_can_parse_exact_version_ranges_via_public_api()
{
var range = SemanticVersionRange.Parse("=1.2.3");
await Assert.That(range.Contains(SemanticVersion.Parse("1.2.3"))).IsTrue();
await Assert.That(range.Contains(SemanticVersion.Parse("1.2.4"))).IsFalse();
await Assert.That(range.ToString("n", null)).IsEqualTo("1.2.3");
}
[Test]
[Arguments(">1.2.3 || <1.0.0", "1.2.4", true)]
[Arguments(">1.2.3 || <1.0.0", "0.9.9", true)]
@ -180,6 +190,7 @@ internal sealed class SemanticVersionRangeTests
[Test]
[Arguments(">=1.0.0 !2.0.0")]
[Arguments("1.2.3 - ")]
[Arguments("!=1.x")]
public async Task I_can_not_parse_invalid_ranges(string range)
{
await Assert.That(() => SemanticVersionRange.Parse(range))

View file

@ -203,7 +203,7 @@ internal sealed class SemanticVersionTests
var obj = new { Version = new SemanticVersion(1, 0, 0, "rc.1", "metadata") };
var json = JsonSerializer.Serialize(obj, RelaxedOptions);
await Assert.That(json).IsEqualTo("{\"Version\":\"1.0.0-rc.1+metadata\"}");
await Assert.That(json).IsEqualTo(/*lang=json,strict*/ "{\"Version\":\"1.0.0-rc.1+metadata\"}");
}
[Test]

View file

@ -57,18 +57,18 @@ public readonly partial record struct SemanticVersionRange : ISpanFormattable
}
var buf = new SpanBuffer(destination);
var groups = _groups ?? [];
var sets = _sets ?? [];
if (format is "m")
{
for (var i = 0; i < groups.Length; i++)
for (var i = 0; i < sets.Length; i++)
{
if (i > 0 && !buf.TryWrite(','))
{
return false;
}
if (!TryFormatMaven(ref buf, groups[i]))
if (!TryFormatMaven(ref buf, sets[i]))
{
return false;
}
@ -76,7 +76,7 @@ public readonly partial record struct SemanticVersionRange : ISpanFormattable
}
else
{
for (var i = 0; i < groups.Length; i++)
for (var i = 0; i < sets.Length; i++)
{
if (i > 0 && !buf.TryWrite(" || "))
{
@ -85,14 +85,14 @@ public readonly partial record struct SemanticVersionRange : ISpanFormattable
if (format is "ns")
{
if (!TryFormatSimpleNpm(ref buf, groups[i]))
if (!TryFormatSimpleNpm(ref buf, sets[i]))
{
return false;
}
}
else
{
if (!TryFormatNormalNpm(ref buf, groups[i]))
if (!TryFormatNormalNpm(ref buf, sets[i]))
{
return false;
}
@ -106,34 +106,34 @@ public readonly partial record struct SemanticVersionRange : ISpanFormattable
#endregion
private static bool TryFormatSimpleNpm(ref SpanBuffer buf, ComparatorGroup group)
private static bool TryFormatSimpleNpm(ref SpanBuffer buf, ConstraintSet set)
{
if (group.Comparators is [])
if (IsAny(set))
{
return buf.TryWrite('*');
}
if (group.Comparators is [{ Op: ComparatorOp.Eq, Version: var version }])
if (set.Constraints is [{ Operation: Comparison.Eq, Version: var version }])
{
return buf.TryWrite(version);
}
if (group.Comparators.Length is not 2)
if (set.Constraints.Length is not 2)
{
return false;
}
if (group is not { Lower: { Op: ComparatorOp.Gte or ComparatorOp.Gt, Version: var lo } })
if (set is not { Lower: { Operation: Comparison.Gte or Comparison.Gt, Version: var lo } })
{
return false;
}
if (group is not { Upper: { Op: ComparatorOp.Lt or ComparatorOp.Lte, Version: var hi } upper })
if (set is not { Upper: { Operation: Comparison.Lt or Comparison.Lte, Version: var hi } upper })
{
return false;
}
if (upper.Op is ComparatorOp.Lt && hi is { Minor: 0, Patch: 0, Prerelease: not { Length: > 0 } })
if (upper.Operation is Comparison.Lt && hi is { Minor: 0, Patch: 0, Prerelease: not { Length: > 0 } })
{
if (lo is { Major: > 0 } && hi.Major == lo.Major + 1)
{
@ -151,7 +151,7 @@ public readonly partial record struct SemanticVersionRange : ISpanFormattable
}
}
if (upper.Op is ComparatorOp.Lt && hi is { Patch: 0, Prerelease: not { Length: > 0 } })
if (upper.Operation is Comparison.Lt && hi is { Patch: 0, Prerelease: not { Length: > 0 } })
{
if (hi.Major == lo.Major && hi.Minor == lo.Minor + 1)
{
@ -162,28 +162,28 @@ public readonly partial record struct SemanticVersionRange : ISpanFormattable
return false;
}
private static bool TryFormatNormalNpm(ref SpanBuffer buf, ComparatorGroup group)
private static bool TryFormatNormalNpm(ref SpanBuffer buf, ConstraintSet set)
{
if (group.Comparators.Length is 0)
if (IsAny(set))
{
return buf.TryWrite('*');
}
for (var i = 0; i < group.Comparators.Length; i++)
for (var i = 0; i < set.Constraints.Length; i++)
{
if (i > 0 && !buf.TryWrite(' '))
{
return false;
}
var @operator = group.Comparators[i].Op switch
var @operator = set.Constraints[i].Operation switch
{
ComparatorOp.Neq => "!=",
ComparatorOp.Lt => "<",
ComparatorOp.Lte => "<=",
ComparatorOp.Gt => ">",
ComparatorOp.Gte => ">=",
_ => ReadOnlySpan<char>.Empty,
Comparison.Neq => "!=",
Comparison.Lt => "<",
Comparison.Lte => "<=",
Comparison.Gt => ">",
Comparison.Gte => ">=",
Comparison.Eq or _ => ReadOnlySpan<char>.Empty,
};
if (!@operator.IsEmpty && !buf.TryWrite(@operator))
@ -191,7 +191,7 @@ public readonly partial record struct SemanticVersionRange : ISpanFormattable
return false;
}
if (!buf.TryWrite(group.Comparators[i].Version))
if (!buf.TryWrite(set.Constraints[i].Version))
{
return false;
}
@ -200,17 +200,27 @@ public readonly partial record struct SemanticVersionRange : ISpanFormattable
return true;
}
private static bool TryFormatMaven(ref SpanBuffer buf, ComparatorGroup group)
private static bool TryFormatMaven(ref SpanBuffer buf, ConstraintSet set)
{
if (group.Comparators is [{ Op: ComparatorOp.Eq, Version: var version }])
if (IsAny(set))
{
return buf.TryWrite("(,)");
}
if (set.Constraints is [{ Operation: Comparison.Eq, Version: var version }])
{
return buf.TryWrite('[') && buf.TryWrite(version) && buf.TryWrite(']');
}
return buf.TryWrite(group.Lower?.Op == ComparatorOp.Gte ? '[' : '(') &&
(!group.Lower.HasValue || buf.TryWrite(group.Lower.Value.Version)) &&
return buf.TryWrite(set.Lower?.Operation == Comparison.Gte ? '[' : '(') &&
(!set.Lower.HasValue || buf.TryWrite(set.Lower.Value.Version)) &&
buf.TryWrite(',') &&
(!group.Upper.HasValue || buf.TryWrite(group.Upper.Value.Version)) &&
buf.TryWrite(group.Upper?.Op == ComparatorOp.Lte ? ']' : ')');
(!set.Upper.HasValue || buf.TryWrite(set.Upper.Value.Version)) &&
buf.TryWrite(set.Upper?.Operation == Comparison.Lte ? ']' : ')');
}
private static bool IsAny(ConstraintSet set)
{
return set.Constraints is [] or [{ Operation: Comparison.Gte, Version: { Major: 0, Minor: 0, Patch: 0 } }];
}
}

View file

@ -1,6 +1,7 @@
// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Geekeey.SemVer;
@ -90,29 +91,29 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
private static bool TryParseNpm(ReadOnlySpan<char> s, out SemanticVersionRange result)
{
result = default;
var groups = new List<ComparatorGroup>();
var sets = new List<ConstraintSet>();
foreach (var range in new NodeSetGrouping(s))
foreach (var range in new NpmSetGrouping(s))
{
if (!TryParseNpmGroup(s[range].Trim(), out var group))
if (!TryParseNpmSet(s[range].Trim(), out var set))
{
return false;
}
groups.Add(group);
sets.Add(set);
}
result = new SemanticVersionRange([.. groups]);
result = new SemanticVersionRange([.. sets]);
return true;
}
private ref struct NodeSetGrouping
private ref struct NpmSetGrouping
{
private readonly ReadOnlySpan<char> _span;
private int _currentStart;
private int _currentEnd;
public NodeSetGrouping(ReadOnlySpan<char> span)
public NpmSetGrouping(ReadOnlySpan<char> span)
{
_span = span;
_currentStart = 0;
@ -136,15 +137,15 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
return true;
}
public readonly NodeSetGrouping GetEnumerator()
public readonly NpmSetGrouping GetEnumerator()
{
return this;
}
}
private static bool TryParseNpmGroup(ReadOnlySpan<char> s, out ComparatorGroup group)
private static bool TryParseNpmSet(ReadOnlySpan<char> s, out ConstraintSet set)
{
group = default;
set = default;
if (s.IsEmpty)
{
@ -152,60 +153,38 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
}
// Hyphen range: "1.0.0 - 2.0.0" OR Caret: "^x.y.z" OR Tilde: "~x.y.z"
if (TryParseHyphenRange(s, out group) || TryParseCaretRange(s, out group) || TryParseTildeRange(s, out group))
if (TryParseHyphenRange(s, out set) || TryParseCaretRange(s, out set) || TryParseTildeRange(s, out set))
{
return true;
}
// Space-separated AND comparators
var comparators = new List<Comparator>();
var tokens = 0;
while (!s.IsEmpty)
var comparators = new List<Constraint>();
foreach (var range in s.Split(' '))
{
// Skip spaces
s = s.TrimStart();
if (s.IsEmpty)
if (s[range] is not { IsEmpty: false } segment)
{
break;
continue;
}
// Find end of this token (next space)
var end = s.IndexOf(' ');
var token = end >= 0 ? s[..end] : s;
tokens++;
if (token.ContainsAny("*xX"))
if (!TryParseCompare(segment, comparators))
{
if (!TryExpandWildcardComparators(token, comparators))
{
return false;
}
return false;
}
else
{
if (!TryParseComparator(token, out var comp))
{
return false;
}
comparators.Add(comp);
}
s = end >= 0 ? s[(end + 1)..] : [];
}
if (comparators.Count is 0 && tokens is 0)
if (comparators.Count is 0)
{
return false;
}
group = new ComparatorGroup([.. comparators]);
set = new ConstraintSet([.. comparators]);
return true;
}
private static bool TryParseHyphenRange(ReadOnlySpan<char> s, out ComparatorGroup group)
private static bool TryParseHyphenRange(ReadOnlySpan<char> s, out ConstraintSet set)
{
group = default;
set = default;
for (var i = 0; i < s.Length; i++)
{
if (s[i..] is [' ', '-', ' ', ..])
@ -220,7 +199,7 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
return false;
}
group = new ComparatorGroup([new Comparator(ComparatorOp.Gte, lo), new Comparator(ComparatorOp.Lte, hi)]);
set = new ConstraintSet([new Constraint(Comparison.Gte, lo), new Constraint(Comparison.Lte, hi)]);
return true;
}
}
@ -228,9 +207,9 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
return false;
}
private static bool TryParseCaretRange(ReadOnlySpan<char> s, out ComparatorGroup group)
private static bool TryParseCaretRange(ReadOnlySpan<char> s, out ConstraintSet set)
{
group = default;
set = default;
if (s.IsEmpty || s[0] is not '^')
{
@ -259,13 +238,13 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
hi = new SemanticVersion(0, 0, lo.Patch + 1);
}
group = new ComparatorGroup([new Comparator(ComparatorOp.Gte, lo), new Comparator(ComparatorOp.Lt, hi)]);
set = new ConstraintSet([new Constraint(Comparison.Gte, lo), new Constraint(Comparison.Lt, hi)]);
return true;
}
private static bool TryParseTildeRange(ReadOnlySpan<char> s, out ComparatorGroup group)
private static bool TryParseTildeRange(ReadOnlySpan<char> s, out ConstraintSet set)
{
group = default;
set = default;
if (s.IsEmpty || s[0] is not '~')
{
@ -290,37 +269,72 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
hi = new SemanticVersion(lo.Major, lo.Minor + 1, 0);
}
group = new ComparatorGroup([new Comparator(ComparatorOp.Gte, lo), new Comparator(ComparatorOp.Lt, hi)]);
set = new ConstraintSet([new Constraint(Comparison.Gte, lo), new Constraint(Comparison.Lt, hi)]);
return true;
}
private static bool TryParseComparator(ReadOnlySpan<char> token, out Comparator result)
private static bool TryParseCompare(ReadOnlySpan<char> s, List<Constraint> constraints)
{
result = default;
var op = Comparison.Eq;
if (!TryParseComparatorPrefix(token.Trim(), out var op, out token))
if (TryParseComparatorPrefix(s, out var comparison, out var rest))
{
if (!SemanticVersion.TryParse(token, null, out var version))
{
return false;
}
op = comparison;
s = rest;
}
result = new Comparator(op, version);
if (!SemanticVersion.TryParsePartially(s, out var lo, out var components))
{
return false;
}
if (components is 0)
{
constraints.Add(new Constraint(Comparison.Gte, new SemanticVersion(0, 0, 0)));
return op is Comparison.Eq;
}
if (components is 3)
{
constraints.Add(new Constraint(op, lo));
return true;
}
SemanticVersion hi;
if (components is 1)
{
hi = new SemanticVersion(lo.Major + 1, 0, 0);
}
else
{
if (!SemanticVersion.TryParse(token, null, out var version))
{
return false;
}
hi = new SemanticVersion(lo.Major, lo.Minor + 1, 0);
}
result = new Comparator(op, version);
return true;
switch (op)
{
case Comparison.Eq:
case Comparison.Gte:
constraints.Add(new Constraint(Comparison.Gte, lo));
constraints.Add(new Constraint(Comparison.Lt, hi));
return true;
case Comparison.Gt:
constraints.Add(new Constraint(Comparison.Gt, lo));
constraints.Add(new Constraint(Comparison.Lt, hi));
return true;
case Comparison.Lte:
constraints.Add(new Constraint(Comparison.Lt, hi));
return true;
case Comparison.Lt:
constraints.Add(new Constraint(Comparison.Lt, lo));
return true;
case Comparison.Neq:
default:
return false;
}
}
private static bool TryParseComparatorPrefix(ReadOnlySpan<char> s, out ComparatorOp op, out ReadOnlySpan<char> remainder)
private static bool TryParseComparatorPrefix(ReadOnlySpan<char> s, out Comparison op, out ReadOnlySpan<char> remainder)
{
if (s.IsEmpty)
{
@ -332,27 +346,27 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
switch (s)
{
case ['!', '=', ..]:
op = ComparatorOp.Neq;
op = Comparison.Neq;
remainder = s[2..];
return true;
case ['>', '=', ..]:
op = ComparatorOp.Gte;
op = Comparison.Gte;
remainder = s[2..];
return true;
case ['<', '=', ..]:
op = ComparatorOp.Lte;
op = Comparison.Lte;
remainder = s[2..];
return true;
case ['>', ..]:
op = ComparatorOp.Gt;
op = Comparison.Gt;
remainder = s[1..];
return true;
case ['<', ..]:
op = ComparatorOp.Lt;
op = Comparison.Lt;
remainder = s[1..];
return true;
case ['=', ..]:
op = ComparatorOp.Eq;
op = Comparison.Eq;
remainder = s[1..];
return true;
default:
@ -362,133 +376,86 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
}
}
private static bool TryExpandWildcardComparators(ReadOnlySpan<char> s, List<Comparator> comparators)
{
s = s.Trim();
if (s is ['*' or 'x' or 'X'])
{
// bare wildcards only make sense when it is an exact version comparison.
return true;
}
var op = ComparatorOp.Eq;
if (TryParseComparatorPrefix(s, out var parsedOp, out var remainder))
{
op = parsedOp;
s = remainder;
}
if (!SemanticVersion.TryParsePartially(s, out var lo, out var wildcard))
{
return false;
}
if (s is ['*' or 'x' or 'X'])
{
// bare wildcards only make sense when it is an exact version comparison.
return op == ComparatorOp.Eq;
}
SemanticVersion hi;
if (wildcard is 1)
{
hi = new SemanticVersion(lo.Major + 1, 0, 0);
}
else
{
hi = new SemanticVersion(lo.Major, lo.Minor + 1, 0);
}
switch (op)
{
case ComparatorOp.Eq:
case ComparatorOp.Gte:
comparators.Add(new Comparator(ComparatorOp.Gte, lo));
comparators.Add(new Comparator(ComparatorOp.Lt, hi));
break;
case ComparatorOp.Gt:
comparators.Add(new Comparator(ComparatorOp.Gt, lo));
comparators.Add(new Comparator(ComparatorOp.Lt, hi));
break;
case ComparatorOp.Lte:
comparators.Add(new Comparator(ComparatorOp.Lt, hi));
break;
case ComparatorOp.Lt:
comparators.Add(new Comparator(ComparatorOp.Lt, lo));
break;
default:
return false;
}
return true;
}
private static bool TryParseMaven(ReadOnlySpan<char> s, out SemanticVersionRange result)
{
result = default;
var groups = new List<ComparatorGroup>();
var sets = new List<ConstraintSet>();
while (!s.IsEmpty)
foreach (var range in new MavenSetGrouping(s))
{
s = s.Trim();
if (s.IsEmpty)
{
break;
}
if (s[0] is not '[' and not '(')
if (!TryParseMavenSet(s[range].Trim(), out var set))
{
return false;
}
// Find matching close bracket
var close = FindMavenClose(s);
if (close < 0)
{
return false;
}
var bracket = s[..(close + 1)];
if (!TryParseMavenGroup(bracket, out var g))
{
return false;
}
groups.Add(g);
s = s[(close + 1)..].Trim();
if (!s.IsEmpty && s[0] == ',')
{
s = s[1..]; // OR separator between groups
}
sets.Add(set);
}
if (groups.Count == 0)
{
return false;
}
result = new SemanticVersionRange([.. groups]);
result = new SemanticVersionRange([.. sets]);
return true;
}
private static int FindMavenClose(ReadOnlySpan<char> s)
private ref struct MavenSetGrouping
{
for (var i = 1; i < s.Length; i++)
private readonly ReadOnlySpan<char> _span;
private int _currentStart;
private int _currentEnd;
public MavenSetGrouping(ReadOnlySpan<char> readOnlySpan)
{
if (s[i] is ']' or ')')
{
return i;
}
_span = readOnlySpan;
_currentStart = 0;
_currentEnd = -1;
}
return -1;
public readonly Range Current => _currentStart.._currentEnd;
public bool MoveNext()
{
_currentStart = _currentEnd is -1 ? 0 : _currentEnd + 1;
if (_currentStart >= _span.Length)
{
return false;
}
var depth = 0;
var i = _currentStart;
while (i < _span.Length)
{
var ch = _span[i];
if (ch is '[' or '(')
{
depth++;
}
else if (ch is ']' or ')')
{
depth--;
}
else if (ch is ',' && depth is 0)
{
_currentEnd = i;
return true;
}
i++;
}
_currentEnd = _span.Length;
return true;
}
public readonly MavenSetGrouping GetEnumerator()
{
return this;
}
}
private static bool TryParseMavenGroup(ReadOnlySpan<char> s, out ComparatorGroup group)
private static bool TryParseMavenSet(ReadOnlySpan<char> s, out ConstraintSet set)
{
group = default;
set = default;
var loInclusive = s[0] is '[';
var hiInclusive = s[^1] is ']';
@ -507,11 +474,11 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
return false;
}
group = new ComparatorGroup([new Comparator(ComparatorOp.Eq, version)]);
set = new ConstraintSet([new Constraint(Comparison.Eq, version)]);
return true;
}
var comps = new List<Comparator>();
var comps = new List<Constraint>();
if (s[..i].Trim() is { IsEmpty: false } loStr)
{
@ -520,7 +487,7 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
return false;
}
comps.Add(new Comparator(loInclusive ? ComparatorOp.Gte : ComparatorOp.Gt, lo));
comps.Add(new Constraint(loInclusive ? Comparison.Gte : Comparison.Gt, lo));
}
if (s[(i + 1)..].Trim() is { IsEmpty: false } hiStr)
@ -530,20 +497,20 @@ public readonly partial record struct SemanticVersionRange : ISpanParsable<Seman
return false;
}
comps.Add(new Comparator(hiInclusive ? ComparatorOp.Lte : ComparatorOp.Lt, hi));
comps.Add(new Constraint(hiInclusive ? Comparison.Lte : Comparison.Lt, hi));
}
// Check for exact match [a,a]
if (comps is [{ Op: ComparatorOp.Gte, Version: var lhs }, { Op: ComparatorOp.Lte, Version: var rhs }])
if (comps is [{ Operation: Comparison.Gte, Version: var lhs }, { Operation: Comparison.Lte, Version: var rhs }])
{
if (lhs == rhs)
{
group = new ComparatorGroup([new Comparator(ComparatorOp.Eq, lhs)]);
set = new ConstraintSet([new Constraint(Comparison.Eq, lhs)]);
return true;
}
}
group = new ComparatorGroup([.. comps]);
set = new ConstraintSet([.. comps]);
return true;
}
}

View file

@ -10,11 +10,11 @@ namespace Geekeey.SemVer;
public readonly partial record struct SemanticVersionRange
{
// OR of AND-groups. null == empty range (matches nothing).
private readonly ComparatorGroup[]? _groups;
private readonly ConstraintSet[]? _sets;
internal SemanticVersionRange(ComparatorGroup[] groups)
internal SemanticVersionRange(ConstraintSet[] sets)
{
_groups = groups;
_sets = sets;
}
/// <summary>
@ -24,70 +24,75 @@ public readonly partial record struct SemanticVersionRange
/// <returns><c>true</c> if the version is contained in the range, otherwise <c>false</c>.</returns>
public bool Contains(SemanticVersion version)
{
if (_groups is null || _groups.Length == 0)
if (_sets is null || _sets.Length == 0)
{
return false;
}
return _groups.Any(group => group.Includes(version));
return _sets.Any(set => set.Includes(version));
}
}
internal enum ComparatorOp { Eq, Neq, Lt, Lte, Gt, Gte }
internal enum Comparison { Eq, Neq, Lt, Lte, Gt, Gte }
internal readonly struct Comparator
internal readonly struct Constraint
{
public readonly ComparatorOp Op;
public readonly Comparison Operation;
public readonly SemanticVersion Version;
public Comparator(ComparatorOp op, SemanticVersion version)
public Constraint(Comparison operation, SemanticVersion version)
{
Op = op;
Operation = operation;
Version = version;
}
public bool Includes(SemanticVersion v)
{
return Op switch
return Operation switch
{
ComparatorOp.Eq => v == Version,
ComparatorOp.Neq => v != Version,
ComparatorOp.Lt => v < Version,
ComparatorOp.Lte => v <= Version,
ComparatorOp.Gt => v > Version,
ComparatorOp.Gte => v >= Version,
Comparison.Eq => v == Version,
Comparison.Neq => v != Version,
Comparison.Lt => v < Version,
Comparison.Lte => v <= Version,
Comparison.Gt => v > Version,
Comparison.Gte => v >= Version,
_ => false
};
}
public override string ToString()
{
return Op switch
return Operation switch
{
ComparatorOp.Neq => $"!={Version}",
ComparatorOp.Lt => $"<{Version}",
ComparatorOp.Lte => $"<={Version}",
ComparatorOp.Gt => $">{Version}",
ComparatorOp.Gte => $">={Version}",
_ => $"{Version}",
Comparison.Neq => $"!={Version}",
Comparison.Lt => $"<{Version}",
Comparison.Lte => $"<={Version}",
Comparison.Gt => $">{Version}",
Comparison.Gte => $">={Version}",
Comparison.Eq or _ => $"{Version}",
};
}
}
// One AND-group of comparators (all must be satisfied).
internal readonly struct ComparatorGroup(Comparator[] comparators)
internal readonly struct ConstraintSet
{
public readonly Comparator[] Comparators = comparators;
public readonly Constraint[] Constraints;
public Comparator? Upper => Comparators.Where(c => c.Op is ComparatorOp.Lt or ComparatorOp.Lte)
.Select(it => (Comparator?)it).FirstOrDefault();
public ConstraintSet(Constraint[] constraints)
{
Constraints = constraints;
}
public Comparator? Lower => Comparators.Where(c => c.Op is ComparatorOp.Gt or ComparatorOp.Gte)
.Select(it => (Comparator?)it).FirstOrDefault();
public Constraint? Upper => Constraints.Where(c => c.Operation is Comparison.Lt or Comparison.Lte)
.Select(it => (Constraint?)it).FirstOrDefault();
public Constraint? Lower => Constraints.Where(c => c.Operation is Comparison.Gt or Comparison.Gte)
.Select(it => (Constraint?)it).FirstOrDefault();
public bool Includes(SemanticVersion v)
{
foreach (var c in Comparators)
foreach (var c in Constraints)
{
if (!c.Includes(v))
{