commandline icon indicating copy to clipboard operation
commandline copied to clipboard

Default supported verbs/options should not return non-zero exit code

Open I3urny opened this issue 3 years ago • 2 comments

This library should not return a non-zero exit code for the default supported verbs and options:

  • help
  • version
  • --help
  • --version
using System;
using CommandLine;

public class Program
{
    [Verb("add", HelpText = "Add file contents to the index.")]
    class AddOptions
    {
        [Option(Default = false, HelpText = "Prints all messages to standard output.")]
        public bool Verbose { get; set; }
    }

    [Verb("commit", HelpText = "Record changes to the repository.")]
    class CommitOptions
    {
    }

    static int Main(string[] args)
    {
        var exitCode = Parser.Default.ParseArguments<AddOptions, CommitOptions>(args)
            .MapResult(
                (AddOptions opts) => 0,
                (CommitOptions opts) => 0,
                errs => 1
            );

        Console.WriteLine($"exitCode = {exitCode}");
        return exitCode;
    }
}

Executing this code results in the following outputs:

> ConsoleApp1.exe version
ConsoleApp1 1.0.0.0
exitCode = 1

> ConsoleApp1.exe help
ConsoleApp1 1.0.0.0
Copyright c  2022

  add        Add file contents to the index.

  commit     Record changes to the repository.

  help       Display more information on a specific command.

  version    Display version information.

exitCode = 1

> ConsoleApp1.exe --version
ConsoleApp1 1.0.0.0
exitCode = 1

> ConsoleApp1.exe --help
ConsoleApp1 1.0.0.0
Copyright c  2022

  add        Add file contents to the index.

  commit     Record changes to the repository.

  help       Display more information on a specific command.

  version    Display version information.

exitCode = 1

> ConsoleApp1.exe help add
ConsoleApp1 1.0.0.0
Copyright c  2022

  --verbose    (Default: false) Prints all messages to standard output.

  --help       Display this help screen.

  --version    Display version information.

exitCode = 1

> ConsoleApp1.exe add --version
ConsoleApp1 1.0.0.0
exitCode = 1

> ConsoleApp1.exe add --help
ConsoleApp1 1.0.0.0
Copyright c  2022

  --verbose    (Default: false) Prints all messages to standard output.

  --help       Display this help screen.

  --version    Display version information.

exitCode = 1

As far as I can tell, those errors can be handled by the consumer as in your code example or by doing it like suggested in #660.

However, since those verbs/options are supported they should not result in an error and therefore also should not require to be handled by the consumer.

I3urny avatar Aug 12 '22 12:08 I3urny

Right, so I took a closer look at the code:

The biggest problem is that .MapResult does not necessarily return an integer but rather a generic type meaning that the library cannot always set an appropriate default value to return for help/version. Returning the default value of a type via the default literal would suffice in my case but that does not necessarily apply to other programs that for example use a custom integer, string or boolean.

Rather, the consumer would have to define a return value for help/version and at that point there is not much of a difference between setting a value for those verbs/options or handling the error in a custom function.

// current way
static int Main(string[] args)
{
    return Parser.Default.ParseArguments<AddOptions, CommitOptions>(args)
        .MapResult(
            (AddOptions opts) => 0,     // value for parsed AddOptions
            (CommitOptions opts) => 0,  // value for parsed CommitOptions
            HandleParseError            // function for notParsed/help/version
        );
}

static int HandleParseError(IEnumerable<Error> errs)
{
    var exitCode = 1;

    if (errs.IsHelp() || errs.IsVersion())
        exitCode = 0;

    return exitCode;
}
// alternative way, if .MapResult returned the default value for the given type
static int Main(string[] args)
{
    return Parser.Default.ParseArguments<AddOptions, CommitOptions>(args)
        .MapResult(
            (AddOptions opts) => 0,     // value for parsed AddOptions
            (CommitOptions opts) => 0,  // value for parsed CommitOptions
            errs => 1                   // value for notParsed
        );
}

// alternative way, if the consumer had to define a default value
static int Main(string[] args)
{
    return Parser.Default.ParseArguments<AddOptions, CommitOptions>(args)
        .MapResult(
            0,                          // value for help/version
            (AddOptions opts) => 0,     // value for parsed AddOptions
            (CommitOptions opts) => 0,  // value for parsed CommitOptions
            errs => 1                   // value for notParsed
        );
}

Unless the maintainers of this project want the .MapResult method to return the default value of the specified type, we will have to handle the "errors" caused by help/version ourselves.

I3urny avatar Sep 04 '22 23:09 I3urny