File: VersionFormatter.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Versioning\NuGet.Versioning.csproj (NuGet.Versioning)
// 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;
using NuGet.Shared;

namespace NuGet.Versioning
{
    /// <summary>
    /// Custom formatter for NuGet versions.
    /// </summary>
    public class VersionFormatter : IFormatProvider, ICustomFormatter
    {
        /// <summary>
        /// A static instance of the VersionFormatter class.
        /// </summary>
        public static readonly VersionFormatter Instance = new();

        /// <summary>
        /// Format a version string.
        /// </summary>
        public string Format(string? format, object? arg, IFormatProvider? formatProvider)
        {
            if (arg == null)
            {
                throw new ArgumentNullException(nameof(arg));
            }

            if (arg is string stringValue)
            {
                return stringValue;
            }

            if (arg is not SemanticVersion version)
            {
                throw ResourcesFormatter.TypeNotSupported(arg.GetType(), nameof(arg));
            }

            if (string.IsNullOrEmpty(format))
            {
                format = "N";
            }

            StringBuilder builder = SharedStringBuilder.Instance.Rent(256);

            foreach (char c in format!)
            {
                Format(builder, c, version);
            }

            return SharedStringBuilder.Instance.ToStringAndReturn(builder);
        }

        /// <summary>
        /// Get version format type.
        /// </summary>
        public object? GetFormat(Type? formatType)
        {
            if (formatType == typeof(ICustomFormatter)
                || formatType == typeof(NuGetVersion)
                || typeof(SemanticVersion).IsAssignableFrom(formatType))
            {
                return this;
            }

            return null;
        }

        private static void Format(StringBuilder builder, char c, SemanticVersion version)
        {
            switch (c)
            {
                case 'N':
                    AppendNormalized(builder, version);
                    return;
                case 'V':
                    AppendVersion(builder, version);
                    return;
                case 'F':
                    AppendFull(builder, version);
                    return;
                case 'R':
                    builder.Append(version.Release);
                    return;
                case 'M':
                    builder.Append(version.Metadata);
                    return;
                case 'x':
                    builder.AppendInt(version.Major);
                    return;
                case 'y':
                    builder.AppendInt(version.Minor);
                    return;
                case 'z':
                    builder.AppendInt(version.Patch);
                    return;
                case 'r':
                    builder.AppendInt(version is NuGetVersion nuGetVersion && nuGetVersion.IsLegacyVersion ? nuGetVersion.Version.Revision : 0);
                    return;

                default:
                    builder.Append(c);
                    return;
            }
        }

        /// <summary>
        /// Appends the full version string including metadata. This is primarily for display purposes.
        /// </summary>
        private static void AppendFull(StringBuilder builder, SemanticVersion version)
        {
            AppendNormalized(builder, version);

            if (version.HasMetadata)
            {
                builder.Append('+');
                builder.Append(version.Metadata);
            }
        }

        /// <summary>
        /// Appends a normalized version string. This string is unique for each version 'identity' 
        /// and does not include leading zeros or metadata.
        /// </summary>
        internal static void AppendNormalized(StringBuilder builder, SemanticVersion version)
        {
            AppendVersion(builder, version);

            if (version.IsPrerelease)
            {
                builder.Append('-');
                builder.Append(version.Release);
            }
        }

        private static void AppendVersion(StringBuilder builder, SemanticVersion version)
        {
            builder.AppendInt(version.Major);
            builder.Append('.');
            builder.AppendInt(version.Minor);
            builder.Append('.');
            builder.AppendInt(version.Patch);

            if (version is NuGetVersion nuGetVersion && nuGetVersion.IsLegacyVersion)
            {
                builder.Append('.');
                builder.AppendInt(nuGetVersion.Version.Revision);
            }
        }
    }
}