commandlineparser / commandline

The best C# command line parser that brings standardized *nix getopt style, for .NET. Includes F# support
MIT License
4.58k stars 478 forks source link

Default verb treated as a value. #794

Open Tinister opened 2 years ago

Tinister commented 2 years ago

This was seen on version 2.8.0 targeting .NET Core 3.1 / 5.

Minimum code sample:

void Main()
{
    var parser = new Parser();
    var result = parser.ParseArguments<CopyArguments>(new[] { "copy", @"A:\", @"C:\", });
    result
        .WithParsed<CopyArguments>(a =>
        {
            Console.WriteLine(a.SourcePath);
            Console.WriteLine(a.DestinationPath);
        });
}

[Verb("copy", isDefault: true)]
class CopyArguments
{
    [Value(0, MetaName = "source", Required = true)]
    public string? SourcePath { get; set; }

    [Value(1, MetaName = "destination", Required = true)]
    public string? DestinationPath { get; set; }
}

This prints "copy" for SourcePath and "A:\" for DestinationPath.

At this point in development I only have one verb (more verbs are planned), and that verb is/will be the default one.

elgonzo commented 2 years ago

Yeah, it's a gotcha when beginning to design the CLI and starting with one verb.

The simple ParseArguments<T> method with a single generic type parameter does not support verb scenarios.

Verb scenarios are supported by either the generic ParseArgument<T1, ...> methods with two or more generic type parameters, or the non-generic ParseArguments(IEnumerable<string> args, params Type[] types) method.

Thus, you could workaround this transient issue by using a second dummy generic type parameter as a placeholder until you get around implementing your other verb(s):

var result = parser.ParseArguments<CopyArguments, object>(new[] { "copy", @"A:\", @"C:\", });

Although, it would be a better approach to just define the options class for your second verb already from the start (it doen't need to carry any properties/options yet) and use it as second generic type parameter for the ParseArguments method, as this would not incur the risk of forgetting to cleanup the dummy type parameters later on...

Note also how the documentation for each of the ParseArgument<T1, ...> methods with two or more generic type parameters and the non-generic ParseArgument method explicitly mentions that these are for "verb commands scenarios" (https://github.com/commandlineparser/commandline/wiki/T_CommandLine_Parser), whereas the documentation for ParseArgument<T> does not mention verb command scenarios.