File: SpanParsableExtensions.cs
Web Access
Project: ..\..\..\src\Cli\Microsoft.DotNet.Cli.CommandLine\Microsoft.DotNet.Cli.CommandLine.csproj (Microsoft.DotNet.Cli.CommandLine)
using System.Collections.Immutable;
using System.CommandLine;
using System.CommandLine.Parsing;
 
namespace Microsoft.DotNet.Cli.CommandLine;
 
public static class SpanParserExtensions
{
    extension<T>(Option<T> o) where T : ISpanParsable<T>
    {
        /// <summary>
        /// Configures the option with a custom parser that uses the <see cref="ISpanParsable{T}"/> implementation to parse the tokens provided.
        /// Will parse a single token with <see cref="ISpanParser{T}.Parse(ReadOnlySpan{char})"/>, and if the option allows multiple tokens will take the 'last one wins' approach.
        /// </summary>
        /// <remarks>
        /// Without this, Options will fall-back to using potentially-reflection-based parsing in S.CL, or
        /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime.
        /// </remarks>
        public Option<T> AsSpanParsable()
        {
            o.CustomParser = StaticSingleItemParser<T>;
            return o;
        }
    }
 
    extension<T>(Option<IReadOnlyCollection<T>> o) where T : ISpanParsable<T>
    {
        /// <summary>
        /// Configures the option with a custom parser that uses the <see cref="ISpanParsable{T}"/> implementation to parse the tokens provided.
        /// This parser handles multiple tokens, using <see cref="ISpanParser{T}.Parse(ReadOnlySpan{char})"/> for each token.
        /// </summary>
        /// <remarks>
        /// Without this, Options will fall-back to using potentially-reflection-based parsing in S.CL, or
        /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime.
        /// </remarks>
        public Option<IReadOnlyCollection<T>> AsSpanParsable()
        {
            o.CustomParser = StaticMultiItemItemParser<T>;
            return o;
        }
    }
 
    extension<T>(Argument<T> a) where T : ISpanParsable<T>
    {
        /// <summary>
        /// Configures the argument with a custom parser that uses the <see cref="ISpanParsable{T}"/> implementation to parse the value.
        /// Will parse a single token with <see cref="ISpanParser{T}.Parse(ReadOnlySpan{char})"/>, and if the argument allows multiple tokens will take the 'last one wins' approach.
        /// </summary>
        /// <remarks>
        /// Without this, Arguments will fall-back to using potentially-reflection-based parsing in S.CL, or
        /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime.
        /// </remarks>
        public Argument<T> AsSpanParsable()
        {
            a.CustomParser = StaticSingleItemParser<T>;
            return a;
        }
    }
 
    extension<T>(Argument<IReadOnlyCollection<T>> a) where T : ISpanParsable<T>
    {
        /// <summary>
        /// Configures the argument with a custom parser that uses the <see cref="ISpanParsable{T}"/> implementation to parse the value.
        /// This parser handles multiple tokens, using <see cref="ISpanParser{T}.Parse(ReadOnlySpan{char})"/> for each token.
        /// </summary>
        /// <remarks>
        /// Without this, Arguments will fall-back to using potentially-reflection-based parsing in S.CL, or
        /// if the type doesn't have built-in S.CL parsing support will fail to parse at runtime.
        /// </remarks>
        public Argument<IReadOnlyCollection<T>> AsSpanParsable()
        {
            a.CustomParser = StaticMultiItemItemParser<T>;
            return a;
        }
    }
 
    internal static IReadOnlyCollection<T>? StaticMultiItemItemParser<T>(ArgumentResult tokenizationResult)
        where T : ISpanParsable<T>
    {
        if (tokenizationResult.Tokens.Count == 0)
        {
            return default;
        }
 
        var parentName =
            tokenizationResult.Parent switch
            {
                OptionResult optionResult => optionResult.Option.Name,
                ArgumentResult argumentResult => argumentResult.Argument.Name,
                CommandResult or null => tokenizationResult.Argument.Name,
                _ => "<unknown>"
            };
        var coll = ImmutableArray.CreateBuilder<T>(tokenizationResult.Tokens.Count);
 
        foreach (var token in tokenizationResult.Tokens)
        {
            var tokenToParse = token.Value;
 
            if (string.IsNullOrEmpty(tokenToParse))
            {
                tokenizationResult.AddError($"Cannot parse null or empty value for symbol '{parentName}'");
                continue;
            }
 
            if (!T.TryParse(tokenToParse, null, out var result))
            {
                tokenizationResult.AddError($"Cannot parse value '{tokenToParse}' for symbol '{parentName}' as a {typeof(T).Name}");
                continue;
            }
 
            coll.Add(result);
        }
 
        return coll.ToImmutableArray();
    }
 
    internal static T? StaticSingleItemParser<T>(ArgumentResult tokenizationResult)
        where T : ISpanParsable<T>
    {
        if (tokenizationResult.Tokens.Count == 0)
        {
            return default;
        }
 
        var parentName =
            tokenizationResult.Parent switch
            {
                OptionResult optionResult => optionResult.Option.Name,
                ArgumentResult argumentResult => argumentResult.Argument.Name,
                CommandResult or null => tokenizationResult.Argument.Name,
                _ => "<unknown>"
            };
        // we explicitly only support parsing one token, so let's do a last-one-wins approach here
        var tokenToParse =
            tokenizationResult.Tokens switch
            {
                [var onlyToken] => onlyToken.Value,
                _ => tokenizationResult.Tokens[^1].Value
            };
 
        if (string.IsNullOrEmpty(tokenToParse))
        {
            tokenizationResult.AddError($"Cannot parse null or empty value for symbol '{parentName}'");
        }
 
        if (!T.TryParse(tokenToParse, null, out var result))
        {
            tokenizationResult.AddError($"Cannot parse value '{tokenToParse}' for symbol '{parentName}' as a {typeof(T).Name}");
        }
 
        return result;
    }
}