command-line-api icon indicating copy to clipboard operation
command-line-api copied to clipboard

No way to distinguish between ParseArgument invoked for user-supplied input and default value

Open SamVanheer opened this issue 4 years ago • 8 comments

When using an option that has a custom parser with isDefault set to true, the custom parser cannot distinguish between being invoked to provide a default value and being invoked to parse input that has no tokens.

Example:

private static IEnumerable<string> Formats { get; } = new List<string>
{
    "foo",
    "bar"
};

var option = new Option<List<string>>("--formats",
    parseArgument: ParseFormats,
    isDefault: true,
    description: "Narrows the operation to process only files with these formats. Supported formats: "
    + string.Join(", ", Formats)));

private static List<string> ParseFormats(ArgumentResult result)
{
    var formats = result.Tokens.Select(t => t.Value).ToList();

    var invalidFormats = formats.Where(t => !Formats.Contains(t));

    if (invalidFormats.Any())
    {
        result.ErrorMessage = $"One or more formats are invalid: {string.Join(", ", invalidFormats)}";
    }
    //Not provided or no formats specified on the command line
    else if (formats.Count == 0)
    {
        return Formats.ToList();
    }

    return formats;
}

(Formats cannot be an enum here because the list in the actual code is constructed from a list of objects providing a string property)

In this case the parser cannot constrain input to require one or more tokens because this will also be the case when getting a default value.

Some way to know when it's being invoked for a default value would be nice.

SamVanheer avatar May 25 '21 13:05 SamVanheer

In this case the parser cannot constrain input to require one or more tokens because this will also be the case when getting a default value.

It sounds to me you want to set the min/max number of required arguments for your --formats option. Unless i misunderstood you, is there a particular reason why you are not setting the option argument arity to achieve this?

elgonzo avatar May 25 '21 14:05 elgonzo

In this case the parser cannot constrain input to require one or more tokens because this will also be the case when getting a default value.

It sounds to me you want to set the min/max number of required arguments for your --formats option. Unless i have misunderstood you, is there a particular reason why you are not setting the option argument arity to achieve this?

Because i can't do that. The Option API doesn't let you set arity when using the custom parser constructor: https://github.com/dotnet/command-line-api/blob/56139ef91d83b1ba86b8ffbd81e0f88298bd21ae/src/System.CommandLine/Option%7BT%7D.cs#L32-L38

SamVanheer avatar May 25 '21 14:05 SamVanheer

Oh dang... :-(

elgonzo avatar May 25 '21 14:05 elgonzo

You can now set Option.Arity.

Another thing you can do is to check whether the ArgumentResult is implicit (indicating it was created via the default value because there were no tokens provided on the command line).

Maybe we should make this property public:

https://github.com/dotnet/command-line-api/blob/b751c2e3cc5aeee1916be9892a2fee5ddbef69fb/src/System.CommandLine/Parsing/ArgumentResult.cs#L30

jonsequitur avatar Jul 14 '21 13:07 jonsequitur

This does the job, thank you. I agree that the IsImplicit property should be made public. Being able to differentiate between the default and a user-provided value could be very useful.

SamVanheer avatar Jul 14 '21 16:07 SamVanheer

Does this still require feedback or was my comment above sufficient?

SamVanheer avatar Aug 25 '23 19:08 SamVanheer

https://github.com/dotnet/command-line-api/pull/2073 may have fixed this, by giving Argument<T> and Option<T> separate DefaultValueFactory and CustomParser properties, each of which has type Func<ArgumentResult, T>?.

KalleOlaviNiemitalo avatar Aug 26 '23 07:08 KalleOlaviNiemitalo

Looks like it, that seems to solve the problem entirely.

SamVanheer avatar Aug 26 '23 07:08 SamVanheer