I鈥檝e not yet debugged into why this is but the current behaviour seems at least unexpected if not wrong.
Using an amended version of the sample code and then adding additional commands CommandA and CommandB, see below:
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
using System;
namespace s.cl
{
class Program
{
static void Main(string[] args)
{
// Create some options and a parser
Option optionThatTakesInt = new Option(
"--int-option",
"An option whose argument is parsed as an int")
{
Argument = new Argument<int>()
};
Option optionThatTakesBool = new Option(
"--bool-option",
"An option whose argument is parsed as a bool")
{
Argument = new Argument<bool>()
};
Option optionThatTakesFileInfo = new Option(
"--file-option",
"An option whose argument is parsed as a FileInfo")
{
Argument = new Argument<FileInfo>()
};
// Add them to the root command
var rootCommand = new RootCommand();
rootCommand.Description = "My sample app";
rootCommand.AddOption(optionThatTakesInt);
rootCommand.AddOption(optionThatTakesBool);
rootCommand.AddOption(optionThatTakesFileInfo);
var subCommandA = new Command("CommandA");
subCommandA.AddAlias("ca");
subCommandA.Handler = CommandHandler.Create<int, bool, FileInfo>((intOption, boolOption, fileOption) =>
{
Console.WriteLine($"Called CommandA");
});
subCommandA.AddOption(new Option("-x", "Indicates whether x"));
subCommandA.TreatUnmatchedTokensAsErrors = false;
rootCommand.AddCommand(subCommandA);
var subCommandB = new Command("CommandB");
subCommandB.AddAlias("cb");
subCommandB.Handler = CommandHandler.Create<int, bool, FileInfo>((intOption, boolOption, fileOption) =>
{
Console.WriteLine($"Called CommandB");
});
subCommandB.AddOption(new Option("-x", "Indicates whether x"));
subCommandB.TreatUnmatchedTokensAsErrors = false;
rootCommand.AddCommand(subCommandB);
rootCommand.Handler = CommandHandler.Create<int, bool, FileInfo>((intOption, boolOption, fileOption) =>
{
Console.WriteLine($"The value for --int-option is: {intOption}");
Console.WriteLine($"The value for --bool-option is: {boolOption}");
Console.WriteLine($"The value for --file-option is: {fileOption?.FullName ?? "null"}");
});
// Parse the incoming args and invoke the handler
Environment.Exit(rootCommand.InvokeAsync(args).Result);
}
}
}
After compiling and running you get the following output
$ dotnet run -- --help
s.cl:
My sample app
Usage:
s.cl [options]
Options:
--int-option <int-option> An option whose argument is parsed as an int
--bool-option An option whose argument is parsed as a bool
--file-option <file-option> An option whose argument is parsed as a FileInfo
--version Display version information
There is no mention of the extra commands, CommandA and CommandB
But if I know the commands are there I can do the following
$ dotnet run -- CommandA
Called CommandA
and then
$ dotnet run -- CommandA --help
Usage:
s.cl CommandA [options] [[--] <additional arguments>...]]
Options:
-x Indicates whether x
Additional Arguments:
Arguments passed to the application that is being run.`
So they are there.
Quite happy that I might be missing something obvious, but its not obvious to me :)
(Thanks for the library though 馃憤 )
If you add a description to the command, it will show up in help.
This looks like an accidental holdover from before Symbol.IsHidden was introduced. Originally, an empty description indicated that the symbol should be hidden from help. The behavior should be updated to behave the way it does for Option, which is that help shows the option even if there's no description:
> dotnet run -- /? My sample app
Usage:
c [options] [command]
Options:
--int-option <int-option>
--bool-option An option whose argument is parsed as a bool
--file-option <file-option> An option whose argument is parsed as a FileInfo --version Display version information
Commands:
ca, CommandA Help for CommandA
Ah, fantastic. Working now thanks.
Is there a ticket for the issue you describe I could follow, potentially contribute to?
We can use this issue to track it. I renamed it to make that clearer and added the "good first issue" and "help wanted" tags.
I'd like to start working on this issue if no one else is doing it at the moment. I'm guessing it's the
ShouldShowHelp method below (in src/System.CommandLine/Help/HelpBuilder.cs) that needs to be changed.
If I understand correctly, this method should return true for all symbols unless the IsHidden flag is set to true?
```
internal bool ShouldShowHelp(ISymbol symbol)
{
if (symbol.IsHidden)
{
return false;
}
if (symbol is IArgument)
{
return !symbol.IsHidden;
}
else
{
return !symbol.IsHidden &&
(!string.IsNullOrWhiteSpace(symbol.Description) ||
symbol.Arguments().Any(ShouldShowHelp));
}
}
That would be great!
That sounds like the correct fix. Let us know if you want any pointers.
I forked the repo, but a couple of tests are failing. Anyone else having the same problems?
The tests seem to fail when calling InvokeMethod or InvokeMethodAsync. More details below.
Test class:
System.CommandLine.DragonFruit.Tests.CommandLineTests
Test cases failing:
It_handles_uncaught_exceptions
It_handles_uncaught_exceptions_synchronously
It_shows_error_without_invoking_method
It_shows_error_without_invoking_method_synchronously
Stack traces start with (same for all tests):
System.ArgumentException : _invocationTarget
at System.CommandLine.Binding.MethodInfoHandlerDescriptor.GetCommandHandler() in /command-line-api/src/System.CommandLine/Binding/MethodInfoHandlerDescriptor.cs:line 47
at System.CommandLine.Invocation.CommandHandler.Create(MethodInfo method, Object target) in /command-line-api/src/System.CommandLine/Invocation/CommandHandler.cs:line 16
I have a fix for those tests here: #591
Great, thank you!
Most helpful comment
If you add a description to the command, it will show up in help.
This looks like an accidental holdover from before
Symbol.IsHiddenwas introduced. Originally, an empty description indicated that the symbol should be hidden from help. The behavior should be updated to behave the way it does forOption, which is that help shows the option even if there's no description: