wip
This commit is contained in:
parent
84b3b5c150
commit
74035d5f13
4 changed files with 938 additions and 682 deletions
|
|
@ -19,7 +19,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -31,7 +31,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -47,7 +47,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -64,7 +64,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -80,7 +80,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -94,7 +94,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -106,7 +106,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -125,7 +125,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -136,7 +136,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -148,7 +148,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
{
|
{
|
||||||
var r = SemanticVersionRange.Parse(range);
|
var r = SemanticVersionRange.Parse(range);
|
||||||
var v = SemanticVersion.Parse(version);
|
var v = SemanticVersion.Parse(version);
|
||||||
await Assert.That(r.Satisfies(v)).IsEqualTo(expected);
|
await Assert.That(r.Contains(v)).IsEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -165,7 +165,7 @@ internal sealed class SemanticVersionRangeTests
|
||||||
var json = "\"^1.2.3\"";
|
var json = "\"^1.2.3\"";
|
||||||
var r = System.Text.Json.JsonSerializer.Deserialize<SemanticVersionRange>(json);
|
var r = System.Text.Json.JsonSerializer.Deserialize<SemanticVersionRange>(json);
|
||||||
await Assert.That(r.ToString()).IsEqualTo("^1.2.3");
|
await Assert.That(r.ToString()).IsEqualTo("^1.2.3");
|
||||||
await Assert.That(r.Satisfies(new SemanticVersion(1, 2, 4))).IsTrue();
|
await Assert.That(r.Contains(new SemanticVersion(1, 2, 4))).IsTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -200,4 +200,15 @@ internal sealed class SemanticVersionRangeTests
|
||||||
await Assert.That(success).IsTrue();
|
await Assert.That(success).IsTrue();
|
||||||
await Assert.That(new string(destination[..charsWritten])).IsEqualTo("^1.2.3");
|
await Assert.That(new string(destination[..charsWritten])).IsEqualTo("^1.2.3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task I_fail_formatting_when_the_tentative_short_form_overflows()
|
||||||
|
{
|
||||||
|
var value = SemanticVersionRange.Parse("[1.2.3,2.0.0)");
|
||||||
|
var destination = new char[5];
|
||||||
|
var success = value.TryFormat(destination, out var charsWritten, "ns", null);
|
||||||
|
|
||||||
|
await Assert.That(success).IsFalse();
|
||||||
|
await Assert.That(charsWritten).IsEqualTo(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
75
src/semver/SemanticVersionRange.Formatting.cs
Normal file
75
src/semver/SemanticVersionRange.Formatting.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) The Geekeey Authors
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Geekeey.SemVer;
|
||||||
|
|
||||||
|
public readonly partial record struct SemanticVersionRange : ISpanFormattable
|
||||||
|
{
|
||||||
|
#region IFormattable
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string ToString(string? format, IFormatProvider? formatProvider)
|
||||||
|
{
|
||||||
|
if (format is not null and not "m" and not "n" and not "ns")
|
||||||
|
{
|
||||||
|
throw new FormatException($"The format string '{format}' is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler = new DefaultInterpolatedStringHandler(0, 1, formatProvider);
|
||||||
|
handler.AppendFormatted(this, format);
|
||||||
|
return handler.ToStringAndClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ISpanFormattable
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to format the semantic version range into the specified span of characters.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="TryFormat(Span{char},out int,ReadOnlySpan{char},IFormatProvider)">ISpanFormattable.TryFormat</see>
|
||||||
|
public bool TryFormat(Span<char> destination, out int charsWritten)
|
||||||
|
{
|
||||||
|
return TryFormat(destination, out charsWritten, default, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
|
||||||
|
{
|
||||||
|
if (format.IsEmpty)
|
||||||
|
{
|
||||||
|
format = "ns";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format is not "m" and not "n" and not "ns")
|
||||||
|
{
|
||||||
|
charsWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new SpanStringBuilder(destination, provider);
|
||||||
|
|
||||||
|
if (_sets is not { Length: > 0 } sets)
|
||||||
|
{
|
||||||
|
builder.AppendLiteral(format is "m" ? "(,)" : "*");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = 0; i < sets.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLiteral(format is "m" ? "," : " || ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sets[i].AppendFormatted(ref builder, format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.TryComplete(out charsWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
462
src/semver/SemanticVersionRange.Parsing.cs
Normal file
462
src/semver/SemanticVersionRange.Parsing.cs
Normal file
|
|
@ -0,0 +1,462 @@
|
||||||
|
// Copyright (c) The Geekeey Authors
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Geekeey.SemVer;
|
||||||
|
|
||||||
|
public readonly partial record struct SemanticVersionRange : ISpanParsable<SemanticVersionRange>
|
||||||
|
{
|
||||||
|
#region IParsable
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a string into a <see cref="SemanticVersionRange"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="Parse(string, IFormatProvider)"/>
|
||||||
|
public static SemanticVersionRange Parse(string s)
|
||||||
|
{
|
||||||
|
return Parse(s.AsSpan(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public static SemanticVersionRange Parse(string s, IFormatProvider? provider)
|
||||||
|
{
|
||||||
|
return Parse(s.AsSpan(), provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to parse a string into a <see cref="SemanticVersionRange"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="TryParse(string, IFormatProvider, out SemanticVersionRange)"/>
|
||||||
|
public static bool TryParse([NotNullWhen(true)] string? s, [MaybeNullWhen(false)] out SemanticVersionRange result)
|
||||||
|
{
|
||||||
|
return TryParse(s, null, out result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out SemanticVersionRange result)
|
||||||
|
{
|
||||||
|
return TryParse(s.AsSpan(), provider, out result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ISpanParsable
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a span of characters into a <see cref="SemanticVersionRange"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="Parse(ReadOnlySpan{char}, IFormatProvider)"/>
|
||||||
|
public static SemanticVersionRange Parse(ReadOnlySpan<char> s)
|
||||||
|
{
|
||||||
|
return Parse(s, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public static SemanticVersionRange Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
|
||||||
|
{
|
||||||
|
if (!TryParse(s, provider, out var result))
|
||||||
|
{
|
||||||
|
throw new FormatException($"The input string '{s}' was not in a correct format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to parse a span of characters into a <see cref="SemanticVersionRange"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="TryParse(ReadOnlySpan{char}, IFormatProvider, out SemanticVersionRange)"/>
|
||||||
|
public static bool TryParse(ReadOnlySpan<char> s, [MaybeNullWhen(false)] out SemanticVersionRange result)
|
||||||
|
{
|
||||||
|
return TryParse(s, null, out result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(false)] out SemanticVersionRange result)
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
|
||||||
|
if (s.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s[0] is '[' or '(' ? TryParseJava(s, out result) : TryParseNode(s, out result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private static bool TryParseJava(ReadOnlySpan<char> s, out SemanticVersionRange result)
|
||||||
|
{
|
||||||
|
var sets = new List<ConstraintSet>();
|
||||||
|
|
||||||
|
foreach (var range in new JavaSetGrouping(s))
|
||||||
|
{
|
||||||
|
if (s[range] is not { IsEmpty: false } part)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryParseJavaSet(part, out var set))
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sets.Add(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new SemanticVersionRange([.. sets]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseJavaSet(ReadOnlySpan<char> s, out ConstraintSet set)
|
||||||
|
{
|
||||||
|
set = default;
|
||||||
|
|
||||||
|
if (s.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s is not ['[' or '(', .., ']' or ')'])
|
||||||
|
{
|
||||||
|
if (!SemanticVersion.TryParsePartially(s, out var version, out _))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set = new ConstraintSet([new Constraint(lower: (version, true), upper: null)]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inclusiveStart = s[0] is '[';
|
||||||
|
var inclusiveEnd = s[^1] is ']';
|
||||||
|
var text = s[1..^1];
|
||||||
|
var comma = text.IndexOf(',');
|
||||||
|
|
||||||
|
if (comma < 0)
|
||||||
|
{
|
||||||
|
if (!inclusiveStart || !inclusiveEnd || !SemanticVersion.TryParsePartially(text.Trim(), out var version, out _))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set = new ConstraintSet([new Constraint(Comparator.Equal, version)]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryCreateBound(text[..comma].Trim(), inclusiveStart, out var lower) ||
|
||||||
|
!TryCreateBound(text[(comma + 1)..].Trim(), inclusiveEnd, out var upper))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set = new ConstraintSet(lower is null && upper is null ? [] : [new Constraint(lower: lower, upper: upper)]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryCreateBound(ReadOnlySpan<char> s, bool inclusive, out (SemanticVersion Version, bool Inclusive)? bound)
|
||||||
|
{
|
||||||
|
if (s.IsEmpty)
|
||||||
|
{
|
||||||
|
bound = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SemanticVersion.TryParsePartially(s, out var version, out _))
|
||||||
|
{
|
||||||
|
bound = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bound = (version, inclusive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ref struct JavaSetGrouping
|
||||||
|
{
|
||||||
|
private readonly ReadOnlySpan<char> _span;
|
||||||
|
private int _currentStart;
|
||||||
|
private int _currentEnd;
|
||||||
|
|
||||||
|
public JavaSetGrouping(ReadOnlySpan<char> readOnlySpan)
|
||||||
|
{
|
||||||
|
_span = readOnlySpan;
|
||||||
|
_currentStart = 0;
|
||||||
|
_currentEnd = -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 JavaSetGrouping GetEnumerator()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseNode(ReadOnlySpan<char> s, out SemanticVersionRange result)
|
||||||
|
{
|
||||||
|
var sets = new List<ConstraintSet>();
|
||||||
|
|
||||||
|
foreach (var range in new NodeSetGrouping(s))
|
||||||
|
{
|
||||||
|
if (s[range] is not { IsEmpty: false } part)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryParseNodeSet(part, out var set))
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sets.Add(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new SemanticVersionRange([.. sets]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseNodeSet(ReadOnlySpan<char> s, out ConstraintSet set)
|
||||||
|
{
|
||||||
|
if (s.IndexOf(" - ") is not -1 and var hyphen)
|
||||||
|
{
|
||||||
|
if (!SemanticVersion.TryParse(s[..hyphen].Trim(), out var lower))
|
||||||
|
{
|
||||||
|
set = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SemanticVersion.TryParse(s[(hyphen + 3)..].Trim(), out var upper))
|
||||||
|
{
|
||||||
|
set = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set = new ConstraintSet([new Constraint(lower: (lower, true), upper: (upper, true))]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = new List<Constraint>(2);
|
||||||
|
var current = s;
|
||||||
|
while (TryReadToken(ref current, out var token))
|
||||||
|
{
|
||||||
|
scoped ReadOnlySpan<char> instruction = [];
|
||||||
|
|
||||||
|
if (token is ">=" or "<=" or ">" or "<" or "~" or "^" or "=" or "!=")
|
||||||
|
{
|
||||||
|
instruction = token;
|
||||||
|
|
||||||
|
if (!TryReadToken(ref current, out token))
|
||||||
|
{
|
||||||
|
set = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (token.StartsWith(">="))
|
||||||
|
{
|
||||||
|
instruction = ">=";
|
||||||
|
token = token[2..];
|
||||||
|
}
|
||||||
|
else if (token.StartsWith("<="))
|
||||||
|
{
|
||||||
|
instruction = "<=";
|
||||||
|
token = token[2..];
|
||||||
|
}
|
||||||
|
else if (token.StartsWith("!="))
|
||||||
|
{
|
||||||
|
instruction = "!=";
|
||||||
|
token = token[2..];
|
||||||
|
}
|
||||||
|
else if (token.StartsWith(">"))
|
||||||
|
{
|
||||||
|
instruction = ">";
|
||||||
|
token = token[1..];
|
||||||
|
}
|
||||||
|
else if (token.StartsWith("<"))
|
||||||
|
{
|
||||||
|
instruction = "<";
|
||||||
|
token = token[1..];
|
||||||
|
}
|
||||||
|
else if (token.StartsWith("^"))
|
||||||
|
{
|
||||||
|
instruction = "^";
|
||||||
|
token = token[1..];
|
||||||
|
}
|
||||||
|
else if (token.StartsWith("~"))
|
||||||
|
{
|
||||||
|
instruction = "~";
|
||||||
|
token = token[1..];
|
||||||
|
}
|
||||||
|
else if (token.StartsWith("="))
|
||||||
|
{
|
||||||
|
instruction = "=";
|
||||||
|
token = token[1..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token is "*" or "x" or "X")
|
||||||
|
{
|
||||||
|
set = new ConstraintSet([default]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryParseNpmConstraint(instruction, token.Trim(), list))
|
||||||
|
{
|
||||||
|
set = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set = new ConstraintSet([.. list]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadToken(ref ReadOnlySpan<char> s, out ReadOnlySpan<char> token)
|
||||||
|
{
|
||||||
|
s = s.TrimStart();
|
||||||
|
if (s.IsEmpty)
|
||||||
|
{
|
||||||
|
token = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var separator = s.IndexOf(' ');
|
||||||
|
if (separator < 0)
|
||||||
|
{
|
||||||
|
token = s;
|
||||||
|
s = default;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
token = s[..separator];
|
||||||
|
s = s[separator..];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseNpmConstraint(ReadOnlySpan<char> op, ReadOnlySpan<char> s, ICollection<Constraint> constraints)
|
||||||
|
{
|
||||||
|
if (!SemanticVersion.TryParsePartially(s.Trim(), out var version, out var components))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case "^":
|
||||||
|
constraints.Add(new Constraint(lower: (version, true), upper: (GetCaretUpperBound(version, components), false)));
|
||||||
|
return true;
|
||||||
|
case "~":
|
||||||
|
constraints.Add(new Constraint(lower: (version, true), upper: (GetTildeUpperBound(version, components), false)));
|
||||||
|
return true;
|
||||||
|
case ">":
|
||||||
|
constraints.Add(new Constraint(Comparator.Greater, version));
|
||||||
|
return true;
|
||||||
|
case ">=":
|
||||||
|
constraints.Add(new Constraint(Comparator.GreaterOrEqual, version));
|
||||||
|
return true;
|
||||||
|
case "<":
|
||||||
|
constraints.Add(new Constraint(Comparator.Less, version));
|
||||||
|
return true;
|
||||||
|
case "<=":
|
||||||
|
constraints.Add(new Constraint(Comparator.LessOrEqual, version));
|
||||||
|
return true;
|
||||||
|
case "!=":
|
||||||
|
constraints.Add(new Constraint(Comparator.NotEqual, version));
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
switch (components)
|
||||||
|
{
|
||||||
|
case 3:
|
||||||
|
constraints.Add(new Constraint(Comparator.Equal, version));
|
||||||
|
return true;
|
||||||
|
case 2:
|
||||||
|
constraints.Add(new Constraint(lower: (version, true), upper: (new SemanticVersion(version.Major, version.Minor + 1, 0), false)));
|
||||||
|
return true;
|
||||||
|
case 1:
|
||||||
|
constraints.Add(new Constraint(lower: (version, true), upper: (new SemanticVersion(version.Major + 1, 0, 0), false)));
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ref struct NodeSetGrouping
|
||||||
|
{
|
||||||
|
private readonly ReadOnlySpan<char> _span;
|
||||||
|
private int _currentStart;
|
||||||
|
private int _currentEnd;
|
||||||
|
|
||||||
|
public NodeSetGrouping(ReadOnlySpan<char> span)
|
||||||
|
{
|
||||||
|
_span = span;
|
||||||
|
_currentStart = 0;
|
||||||
|
_currentEnd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Range Current => _currentStart.._currentEnd;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
_currentStart = _currentEnd is -1 ? 0 : _currentEnd + 2;
|
||||||
|
|
||||||
|
if (_currentStart >= _span.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = _span[_currentStart..].IndexOf("||");
|
||||||
|
_currentEnd = index >= 0 ? _currentStart + index : _span.Length;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly NodeSetGrouping GetEnumerator()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue