dotnet / dotnet/command-line-api

Exception occurs when Argument<bool> is omitted

Open
#2,295 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
3.7k
Forks
428
PR merge metrics
No merged PRs in 30d

Description

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 [ []] [options]

Arguments:
Source image, either .cue or .iso file.
File to read from the file system.
Directory to write the read file to.
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 [ []] [options]

Arguments:
Source image, either .cue or .iso file.
File to read from the file system.
Directory to write the read file to.
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.b__0(InvocationContext context)
at System.CommandLine.Invocation.AnonymousCommandHandler.InvokeAsync(InvocationContext context)
at System.CommandLine.Invocation.InvocationPipeline.<>c__DisplayClass4_0.<b__0>d.MoveNext()
--- End of stack trace from previous location ---
at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass17_0.<b__0>d.MoveNext()
--- End of stack trace from previous location ---
at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass12_0.<b__0>d.MoveNext()
--- End of stack trace from previous location ---
at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass22_0.<b__0>d.MoveNext()
--- End of stack trace from previous location ---
at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass19_0.<b__0>d.MoveNext()
--- End of stack trace from previous location ---
at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<b__18_0>d.MoveNext()
--- End of stack trace from previous location ---
at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass16_0.<b__0>d.MoveNext()
--- End of stack trace from previous location ---
at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<b__5_0>d.MoveNext()
--- End of stack trace from previous location ---
at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass8_0.<b__0>d.MoveNext()
```

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

```
var cooked = new Argument(
"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 [ []] [options]

Arguments:
Source image, either .cue or .iso file.
File to read from the file system.
Directory to write the read file to.
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 Source = new(
"source",
"Source image, either .cue or .iso file.");

public static async Task 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(
"target",
"File to read from the file system."
);

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

var cooked = new Argument(
"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(
"number",
"Track to read from the disc image."
);

var output = new Argument(
"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}");
}
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.