dotnet / command-line-api

Command line parsing, invocation, and rendering of terminal output.
https://github.com/dotnet/command-line-api/wiki
MIT License
3.4k stars 381 forks source link

Exception occurs when Argument<bool> is omitted #2295

Open aybe opened 11 months ago

aybe commented 11 months ago

For a command line like so: app.exe string string string bool, when you omit the bool parameter, an exception occurs.

It should fail gracefully, telling Required argument missing for command: 'system'. instead of crashing.

Also, the bool argument in usage has extra square brackets which I suppose means it's optional while it isn't in reality.

Help for the command:

C:\GitHub\ISO9660\ISO9660.CLI\bin\Debug\net7.0>ISO9660.CLI.exe read system             
Required argument missing for command: 'system'.
Required argument missing for command: 'system'.
Required argument missing for command: 'system'.

Description:
  Reads a file from the file system.

Usage:
  ISO9660.CLI read system [<source> <target> <output> [<cooked>]] [options]

Arguments:
  <source>  Source image, either .cue or .iso file.
  <target>  File to read from the file system.
  <output>  Directory to write the read file to.
  <cooked>  Extract file as user data (true) or in raw mode (false).

Options:
  -?, -h, --help  Show help and usage information

Invoking it with last string missing works fine, tells argument is missing:

C:\GitHub\ISO9660\ISO9660.CLI\bin\Debug\net7.0>ISO9660.CLI.exe read system a b
Required argument missing for command: 'system'.

Description:
  Reads a file from the file system.

Usage:
  ISO9660.CLI read system [<source> <target> <output> [<cooked>]] [options]

Arguments:
  <source>  Source image, either .cue or .iso file.
  <target>  File to read from the file system.
  <output>  Directory to write the read file to.
  <cooked>  Extract file as user data (true) or in raw mode (false).

Options:
  -?, -h, --help  Show help and usage information

Invoking it with bool argument missing, it crashes:

C:\GitHub\ISO9660\ISO9660.CLI\bin\Debug\net7.0>ISO9660.CLI.exe read system a b c
Unhandled exception: System.InvalidOperationException: Cannot parse argument 'b' for command 'system' as expected type 'System.Boolean'.
   at System.CommandLine.Binding.ArgumentConverter.GetValueOrDefault[T](ArgumentConversionResult result)
   at System.CommandLine.Parsing.ArgumentResult.GetValueOrDefault[T]()
   at System.CommandLine.Parsing.SymbolResult.GetValueForArgument[T](Argument`1 argument)
   at System.CommandLine.Parsing.ParseResult.GetValueForArgument[T](Argument`1 argument)
   at System.CommandLine.Parsing.ParseResult.GetValueFor[T](IValueDescriptor`1 symbol)
   at System.CommandLine.Handler.GetValueForHandlerParameter[T](IValueDescriptor`1 symbol, InvocationContext context)
   at System.CommandLine.Handler.<>c__DisplayClass16_0`4.<SetHandler>b__0(InvocationContext context)    
   at System.CommandLine.Invocation.AnonymousCommandHandler.InvokeAsync(InvocationContext context)      
   at System.CommandLine.Invocation.InvocationPipeline.<>c__DisplayClass4_0.<<BuildInvocationChain>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass17_0.<<UseParseErrorReporting>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass12_0.<<UseHelp>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass22_0.<<UseVersionOption>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass19_0.<<UseTypoCorrections>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<<UseSuggestDirective>b__18_0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass16_0.<<UseParseDirective>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<<RegisterWithDotnetSuggest>b__5_0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass8_0.<<UseExceptionHandler>b__0>d.MoveNext()

But if one uses SetDefaultValue, arguments help is now correct and no more crash:

        var cooked = new Argument<bool>(
            "cooked", 
            "Extract file as user data (true) or in raw mode (false)."
        );

        cooked.SetDefaultValue(true); // BUG in System.CommandLine
C:\GitHub\ISO9660\ISO9660.CLI\bin\Debug\net7.0>ISO9660.CLI.exe read system a b
Required argument missing for command: 'system'.

Description:
  Reads a file from the file system.

Usage:
  ISO9660.CLI read system [<source> <target> <output> [<cooked>]] [options]

Arguments:
  <source>  Source image, either .cue or .iso file.
  <target>  File to read from the file system.
  <output>  Directory to write the read file to.
  <cooked>  Extract file as user data (true) or in raw mode (false). [default: True]

Options:
  -?, -h, --help  Show help and usage information
C:\GitHub\ISO9660\ISO9660.CLI\bin\Debug\net7.0>ISO9660.CLI.exe read system a b c
TODO read system: a, b, c, True

Complete example:

using System.CommandLine;

namespace ISO9660.CLI;

internal static class Program
{
    private static readonly Argument<string> Source = new(
        "source",
        "Source image, either .cue or .iso file.");

    public static async Task<int> Main(string[] args)
    {
        var root = new RootCommand("CD-ROM image reader.")
        {
            BuildList(),
            BuildRead()
        };

        return await root.InvokeAsync(args);
    }

    private static Command BuildList()
    {
        return new Command("list", "Listing mode.")
        {
            BuildListSystem(),
            BuildListTracks()
        };
    }

    private static Command BuildListSystem()
    {
        var system = new Command("system", "Lists the files in the file system.")
        {
            Source
        };

        system.SetHandler(StartListSystem, Source);

        return system;
    }

    private static Command BuildListTracks()
    {
        var command = new Command("tracks", "Lists the tracks in the disc image.")
        {
            Source
        };

        command.SetHandler(StartListTracks, Source);

        return command;
    }

    private static Command BuildRead()
    {
        return new Command("read", "Reading mode.")
        {
            BuildReadSystem(),
            BuildReadTracks()
        };
    }

    private static Command BuildReadSystem()
    {
        var target = new Argument<string>(
            "target",
            "File to read from the file system."
        );

        var output = new Argument<string>(
            "output",
            "Directory to write the read file to."
        );

        var cooked = new Argument<bool>(
            "cooked",
            "Extract file as user data (true) or in raw mode (false)."
        );

        var command = new Command("system", "Reads a file from the file system.")
        {
            Source, target, output, cooked
        };

        command.SetHandler(StartReadSystem, Source, target, output, cooked);

        return command;
    }

    private static Command BuildReadTracks()
    {
        var number = new Argument<int>(
            "number",
            "Track to read from the disc image."
        );

        var output = new Argument<string>(
            "output",
            "Directory to write the read track to."
        );

        var command = new Command("tracks", "Reads a track from the disc image.")
        {
            Source, number, output
        };

        command.SetHandler(StartReadTracks, Source, number, output);

        return command;
    }

    private static async Task StartListSystem(string source)
    {
        Console.WriteLine($"TODO list system: {source}");
    }

    private static async Task StartListTracks(string source)
    {
        Console.WriteLine($"TODO list tracks: {source}");
    }

    private static async Task StartReadSystem(string source, string target, string output, bool cooked)
    {
        Console.WriteLine($"TODO read system: {source}, {target}, {output}, {cooked}");
    }

    private static async Task StartReadTracks(string source, int number, string output)
    {
        Console.WriteLine($"TODO read tracks: {source}, {number}, {output}");
    }
}
elgonzo commented 11 months ago

Note that the library has changed significantly since the version you are using was made available. These changes also encompass significant change of the public API surface of the library. Among other things, option/argument/command types are now prefixed with "Cli" and feature a streamlined and more concise set of properties, command handlers have been substituted by actions, the invocation pipeline appearing in your stack trace has also been eliminated.

Thus, i'd suggest you either wait for a proper non-beta release (whenever that will be) or migrate to the currently available beta and see whether the issue still occurs with the current beta.

Note that new/current betas are so far not accessible through the nuget.org feed, but through a separate "daily builds" nuget feed that is mentioned in the readme.md. Latest version available through this feed is currently 2.0.0-beta4.23407.1.

Also note that there is little to no documentation available yet for the new API surface. If you choose to migrate now to the latest beta version(s) from the "daily builds" feed, be prepared spending time on scouring the issue tracker here for comments and discussions relating to the new APIs and studying the source code of the library itself to learn what the new APIs are and how they can be used...

P.S.: I am not involved with the project and not otherwise associated with the project maintainers. I am just a user, and as such i am unable to tell anything about ETAs or the project schedule regarding non-beta release or documentation, unfortunately. :-(

aybe commented 11 months ago

Thanks for the information, I'll stick to my // BUG and wait for that new API to surface in nuget.org :)