Files
AIC-Project/Client/PackageLocal/com.unity.pipeline@0.4.0-exp.1/Editor/Commands/PackageManager/PackageIdentifier.cs
T

162 lines
6.5 KiB
C#

using System;
namespace Unity.Pipeline.Editor.Commands.PackageManager
{
/// <summary>Where an added package comes from, derived from its identifier string (CLI-203).</summary>
public enum PackageSourceKind
{
/// <summary>Could not be classified (e.g. empty input).</summary>
Unknown,
/// <summary>A registry package name, optionally pinned: <c>com.unity.foo</c> or <c>com.unity.foo@1.2.3</c>.</summary>
Registry,
/// <summary>A git URL: <c>https://…​.git</c>, <c>git+…</c>, <c>ssh://…</c>, <c>git@host:…</c>, optionally <c>#revision</c>.</summary>
Git,
/// <summary>A local package reference: <c>file:../RelativePath</c> or <c>file:/abs/path</c>.</summary>
Local
}
/// <summary>A parsed UPM <c>package_add</c> identifier — the normalized string handed to
/// <c>Client.Add</c> plus the pieces extracted for plan/audit text.</summary>
public sealed class ParsedPackageId
{
/// <summary>How the package will be resolved by UPM.</summary>
public PackageSourceKind Kind { get; set; }
/// <summary>The identifier passed verbatim to <c>UnityEditor.PackageManager.Client.Add</c>.</summary>
public string Identifier { get; set; }
/// <summary>Registry package name (Registry only); null otherwise.</summary>
public string Name { get; set; }
/// <summary>Registry version or git revision when given; null otherwise.</summary>
public string Version { get; set; }
/// <summary>Human-readable summary for plan/audit text.</summary>
public string Description { get; set; }
}
/// <summary>
/// Pure, editor-independent classification of a <c>package_add</c> identifier into a registry name,
/// git URL, or local path (CLI-203). Kept free of any Unity API so the add-path's input handling is
/// unit-testable without a live editor; the command layer feeds the result to
/// <c>UnityEditor.PackageManager.Client.Add</c>.
/// </summary>
public static class PackageIdentifier
{
/// <summary>Classify an identifier without parsing out its parts.</summary>
public static PackageSourceKind Classify(string identifier)
{
if (string.IsNullOrWhiteSpace(identifier))
return PackageSourceKind.Unknown;
var id = identifier.Trim();
if (StartsWith(id, "file:"))
return PackageSourceKind.Local;
// Explicit git forms and any URL scheme (UPM treats http(s) identifiers as git URLs).
if (StartsWith(id, "git+") || StartsWith(id, "ssh://") || id.StartsWith("git@", StringComparison.Ordinal))
return PackageSourceKind.Git;
if (StartsWith(id, "http://") || StartsWith(id, "https://") || id.Contains("://"))
return PackageSourceKind.Git;
// A bare "owner/repo.git" (no scheme) is still a git reference. Strip an optional
// "#revision" before testing the suffix.
var core = id;
var hash = core.IndexOf('#');
if (hash >= 0)
core = core.Substring(0, hash);
if (core.EndsWith(".git", StringComparison.OrdinalIgnoreCase))
return PackageSourceKind.Git;
return PackageSourceKind.Registry;
}
/// <summary>
/// Classify and split <paramref name="identifier"/>. Returns false with <paramref name="error"/>
/// set for empty/unclassifiable input.
/// </summary>
public static bool TryParse(string identifier, out ParsedPackageId parsed, out string error)
{
parsed = null;
error = null;
if (string.IsNullOrWhiteSpace(identifier))
{
error = "Package identifier is empty. Provide a name (com.unity.foo[@version]), a git URL, or a file: path.";
return false;
}
var id = identifier.Trim();
switch (Classify(id))
{
case PackageSourceKind.Local:
parsed = new ParsedPackageId
{
Kind = PackageSourceKind.Local,
Identifier = id,
Description = $"local package '{id}'"
};
return true;
case PackageSourceKind.Git:
{
string revision = null;
var hash = id.IndexOf('#');
if (hash >= 0 && hash < id.Length - 1)
revision = id.Substring(hash + 1);
parsed = new ParsedPackageId
{
Kind = PackageSourceKind.Git,
Identifier = id,
Version = revision,
Description = revision != null ? $"git package '{id}' @ {revision}" : $"git package '{id}'"
};
return true;
}
case PackageSourceKind.Registry:
{
var name = id;
string version = null;
// Split on the version separator '@'. Use the last '@' so npm-scoped names
// ("@scope/pkg@1.0.0") still split on the version, not the leading scope marker.
var at = id.LastIndexOf('@');
if (at > 0)
{
name = id.Substring(0, at);
version = id.Substring(at + 1);
}
if (string.IsNullOrWhiteSpace(name))
{
error = $"Package name is empty in '{identifier}'.";
return false;
}
parsed = new ParsedPackageId
{
Kind = PackageSourceKind.Registry,
Identifier = id,
Name = name,
Version = version,
Description = version != null ? $"'{name}' @ {version}" : $"'{name}' (latest compatible)"
};
return true;
}
default:
error = $"Could not classify package identifier '{identifier}'.";
return false;
}
}
private static bool StartsWith(string value, string prefix) =>
value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
}
}