Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,197 @@
using System.Linq;
using System.Text;
using NuGet.Versioning;
using StellaOps.Concelier.Normalization.Identifiers;
namespace StellaOps.Concelier.Connector.Common.Packages;
/// <summary>
/// Shared helpers for working with Package URLs and SemVer coordinates inside connectors.
/// </summary>
public static class PackageCoordinateHelper
{
public static bool TryParsePackageUrl(string? value, out PackageCoordinates? coordinates)
{
coordinates = null;
if (!IdentifierNormalizer.TryNormalizePackageUrl(value, out var canonical, out var packageUrl) || packageUrl is null)
{
return false;
}
var namespaceSegments = packageUrl.NamespaceSegments.ToArray();
var subpathSegments = packageUrl.SubpathSegments.ToArray();
var qualifiers = packageUrl.Qualifiers.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
var canonicalRebuilt = BuildPackageUrl(
packageUrl.Type,
namespaceSegments,
packageUrl.Name,
packageUrl.Version,
qualifiers,
subpathSegments);
coordinates = new PackageCoordinates(
Canonical: canonicalRebuilt,
Type: packageUrl.Type,
NamespaceSegments: namespaceSegments,
Name: packageUrl.Name,
Version: packageUrl.Version,
Qualifiers: qualifiers,
SubpathSegments: subpathSegments,
Original: packageUrl.Original);
return true;
}
public static PackageCoordinates ParsePackageUrl(string value)
{
if (!TryParsePackageUrl(value, out var coordinates) || coordinates is null)
{
throw new FormatException($"Value '{value}' is not a valid Package URL");
}
return coordinates;
}
public static bool TryParseSemVer(string? value, out SemanticVersion? version, out string? normalized)
{
version = null;
normalized = null;
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
if (!SemanticVersion.TryParse(value.Trim(), out var parsed))
{
return false;
}
version = parsed;
normalized = parsed.ToNormalizedString();
return true;
}
public static bool TryParseSemVerRange(string? value, out VersionRange? range)
{
range = null;
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
var trimmed = value.Trim();
if (trimmed.StartsWith("^", StringComparison.Ordinal))
{
var baseSegment = trimmed[1..];
if (!SemanticVersion.TryParse(baseSegment, out var baseVersion))
{
return false;
}
var upperBound = CalculateCaretUpperBound(baseVersion);
var caretExpression = $"[{baseVersion.ToNormalizedString()}, {upperBound.ToNormalizedString()})";
if (VersionRange.TryParse(caretExpression, out var caretRange))
{
range = caretRange;
return true;
}
return false;
}
if (!VersionRange.TryParse(trimmed, out var parsed))
{
try
{
parsed = VersionRange.Parse(trimmed);
}
catch
{
return false;
}
}
range = parsed;
return true;
}
public static string BuildPackageUrl(
string type,
IReadOnlyList<string>? namespaceSegments,
string name,
string? version = null,
IReadOnlyDictionary<string, string>? qualifiers = null,
IReadOnlyList<string>? subpathSegments = null)
{
ArgumentException.ThrowIfNullOrEmpty(type);
ArgumentException.ThrowIfNullOrEmpty(name);
var builder = new StringBuilder("pkg:");
builder.Append(type.Trim().ToLowerInvariant());
builder.Append('/');
if (namespaceSegments is not null && namespaceSegments.Count > 0)
{
builder.Append(string.Join('/', namespaceSegments.Select(NormalizeSegment)));
builder.Append('/');
}
builder.Append(NormalizeSegment(name));
if (!string.IsNullOrWhiteSpace(version))
{
builder.Append('@');
builder.Append(version.Trim());
}
if (qualifiers is not null && qualifiers.Count > 0)
{
builder.Append('?');
builder.Append(string.Join('&', qualifiers
.OrderBy(static kvp => kvp.Key, StringComparer.OrdinalIgnoreCase)
.Select(kvp => $"{NormalizeSegment(kvp.Key)}={NormalizeSegment(kvp.Value)}")));
}
if (subpathSegments is not null && subpathSegments.Count > 0)
{
builder.Append('#');
builder.Append(string.Join('/', subpathSegments.Select(NormalizeSegment)));
}
return builder.ToString();
}
private static string NormalizeSegment(string value)
{
ArgumentNullException.ThrowIfNull(value);
var trimmed = value.Trim();
var unescaped = Uri.UnescapeDataString(trimmed);
var encoded = Uri.EscapeDataString(unescaped);
return encoded.Replace("%40", "@");
}
private static SemanticVersion CalculateCaretUpperBound(SemanticVersion baseVersion)
{
if (baseVersion.Major > 0)
{
return new SemanticVersion(baseVersion.Major + 1, 0, 0);
}
if (baseVersion.Minor > 0)
{
return new SemanticVersion(0, baseVersion.Minor + 1, 0);
}
return new SemanticVersion(0, 0, baseVersion.Patch + 1);
}
}
public sealed record PackageCoordinates(
string Canonical,
string Type,
IReadOnlyList<string> NamespaceSegments,
string Name,
string? Version,
IReadOnlyDictionary<string, string> Qualifiers,
IReadOnlyList<string> SubpathSegments,
string Original);