|
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
namespace NuGet.Versioning
{
/// <summary>
/// Custom formatter for NuGet <see cref="VersionRange"/>.
/// </summary>
public class VersionRangeFormatter : IFormatProvider, ICustomFormatter
{
/// <summary>
/// A static instance of the <see cref="VersionRangeFormatter"/> class.
/// </summary>
public static readonly VersionRangeFormatter Instance = new VersionRangeFormatter();
/// <summary>
/// Format a version range string.
/// </summary>
public string Format(string? format, object? arg, IFormatProvider? formatProvider)
{
if (arg == null)
{
throw new ArgumentNullException(nameof(arg));
}
if (arg is not VersionRange range)
{
throw ResourcesFormatter.TypeNotSupported(arg.GetType(), nameof(arg));
}
if (string.IsNullOrEmpty(format))
{
format = "N";
}
var builder = SharedStringBuilder.Instance.Rent(256);
for (var i = 0; i < format!.Length; i++)
{
Format(builder, format[i], range);
}
return SharedStringBuilder.Instance.ToStringAndReturn(builder);
}
/// <summary>
/// Format type.
/// </summary>
public object? GetFormat(Type? formatType)
{
if (typeof(VersionRange).IsAssignableFrom(formatType))
{
return this;
}
return null;
}
private static void Format(StringBuilder builder, char c, VersionRange range)
{
switch (c)
{
case 'P':
PrettyPrint(builder, range, useParentheses: true, showFloating: false);
break;
case 'p':
PrettyPrint(builder, range, useParentheses: false, showFloating: false);
break;
case 'F':
PrettyPrint(builder, range, useParentheses: true, showFloating: true);
break;
case 'f':
PrettyPrint(builder, range, useParentheses: false, showFloating: true);
break;
case 'L':
if (range.HasLowerBound)
{
VersionFormatter.AppendNormalized(builder, range.MinVersion);
}
break;
case 'U':
if (range.HasUpperBound)
{
VersionFormatter.AppendNormalized(builder, range.MaxVersion);
}
break;
case 'S':
GetToString(builder, range);
break;
case 'N':
GetNormalizedString(builder, range);
break;
case 'D':
GetLegacyString(builder, range);
break;
case 'T':
GetLegacyShortString(builder, range);
break;
case 'A':
GetShortString(builder, range);
break;
default:
builder.Append(c);
break;
}
}
private static void GetShortString(StringBuilder builder, VersionRange range)
{
if (range.HasLowerBound
&& range.IsMinInclusive
&& !range.HasUpperBound)
{
if (range.IsFloating)
{
range.Float.ToString(builder);
}
else
{
VersionFormatter.AppendNormalized(builder, range.MinVersion);
}
}
else if (range.HasLowerAndUpperBounds
&& range.IsMinInclusive
&& range.IsMaxInclusive
&& range.MinVersion.Equals(range.MaxVersion))
{
// Floating should be ignored here.
builder.Append('[');
VersionFormatter.AppendNormalized(builder, range.MinVersion);
builder.Append(']');
}
else
{
GetNormalizedString(builder, range);
}
}
/// <summary>
/// Builds a normalized string with no short hand
/// </summary>
private static void GetNormalizedString(StringBuilder builder, VersionRange range)
{
builder.Append(range.HasLowerBound && range.IsMinInclusive ? '[' : '(');
if (range.HasLowerBound)
{
if (range.IsFloating)
{
range.Float.ToString(builder);
}
else
{
VersionFormatter.AppendNormalized(builder, range.MinVersion);
}
}
builder.Append(", ");
if (range.HasUpperBound)
{
VersionFormatter.AppendNormalized(builder, range.MaxVersion);
}
builder.Append(range.HasUpperBound && range.IsMaxInclusive ? ']' : ')');
}
/// <summary>
/// Builds a string to represent the VersionRange. This string can include short hand notations.
/// </summary>
private static void GetToString(StringBuilder builder, VersionRange range)
{
if (range.HasLowerBound
&& range.IsMinInclusive
&& !range.HasUpperBound)
{
VersionFormatter.AppendNormalized(builder, range.MinVersion);
}
else if (range.HasLowerAndUpperBounds
&& range.IsMinInclusive
&& range.IsMaxInclusive
&& range.MinVersion.Equals(range.MaxVersion))
{
// TODO: Does this need a specific version comparison? Does metadata matter?
builder.Append('[');
VersionFormatter.AppendNormalized(builder, range.MinVersion);
builder.Append(']');
}
else
{
GetNormalizedString(builder, range);
}
}
/// <summary>
/// Creates a legacy short string that is compatible with NuGet 2.8.3
/// </summary>
private static void GetLegacyShortString(StringBuilder builder, VersionRangeBase range)
{
if (range.HasLowerBound
&& range.IsMinInclusive
&& !range.HasUpperBound)
{
VersionFormatter.AppendNormalized(builder, range.MinVersion);
}
else if (range.HasLowerAndUpperBounds
&& range.IsMinInclusive
&& range.IsMaxInclusive
&& range.MinVersion.Equals(range.MaxVersion))
{
builder.Append('[');
VersionFormatter.AppendNormalized(builder, range.MinVersion);
builder.Append(']');
}
else
{
GetLegacyString(builder, range);
}
}
/// <summary>
/// Creates a legacy string that is compatible with NuGet 2.8.3
/// </summary>
private static void GetLegacyString(StringBuilder builder, VersionRangeBase range)
{
builder.Append(range.HasLowerBound && range.IsMinInclusive ? '[' : '(');
if (range.HasLowerBound)
{
VersionFormatter.AppendNormalized(builder, range.MinVersion);
}
builder.Append(", ");
if (range.HasUpperBound)
{
VersionFormatter.AppendNormalized(builder, range.MaxVersion);
}
builder.Append(range.HasUpperBound && range.IsMaxInclusive ? ']' : ')');
}
/// <summary>
/// A pretty print representation of the VersionRange.
/// </summary>
private static void PrettyPrint(StringBuilder builder, VersionRange range, bool useParentheses, bool showFloating)
{
if (!range.HasLowerBound
&& !range.HasUpperBound)
{
// empty range
return;
}
if (useParentheses)
{
builder.Append('(');
}
if (range.HasLowerAndUpperBounds
&& range.MaxVersion.Equals(range.MinVersion)
&& range.IsMinInclusive
&& range.IsMaxInclusive)
{
// single version
builder.Append("= ");
VersionFormatter.AppendNormalized(builder, range.MinVersion);
}
else
{
// normal case with a lower, upper, or both.
if (range.HasLowerBound)
{
PrettyPrintBound(builder, range.MinVersion, range.IsMinInclusive, ">", range.IsFloating && showFloating ? range.Float : null);
}
if (range.HasLowerAndUpperBounds)
{
builder.Append(" && ");
}
if (range.HasUpperBound)
{
PrettyPrintBound(builder, range.MaxVersion, range.IsMaxInclusive, "<", floatRange: null);
}
}
if (useParentheses)
{
builder.Append(')');
}
}
private static void PrettyPrintBound(StringBuilder builder, NuGetVersion version, bool inclusive, string boundChar, FloatRange? floatRange)
{
builder.Append(boundChar);
if (inclusive)
{
builder.Append("= ");
}
else
{
builder.Append(' ');
}
if (floatRange != null)
{
floatRange.ToString(builder);
}
else
{
VersionFormatter.AppendNormalized(builder, version);
}
}
}
}
|