1

Scenario: I want to parse a console app's command line that has a number of options (that don't relate to a command), and a command that has a number of options. I've simplified what I'm trying to into a fictitious example. E.g. myapp --i infile.dat --o outfile.dat conversion --threshold 42

Problem: I'm finding that if I put the "conversion" command and it's option on the command-line then only its handler gets called, but not the handler for the root command. So I have no way to determine the values of the root command's --i and --o options.

(Conversely, if I omit the "conversion" command then root command's handler only is called - which is what i would expect.)

Example code:

public class Program
{
    public static async Task<int> Main(string[] args)
    {
        // Conversion command
        var thresholdOpt = new Option<int>("--threshold");
        var conversionCommand = new Command("conversion") { thresholdOpt };
        conversionCommand.SetHandler(
            (int threshold) => { Console.WriteLine($"threshold={threshold}"); },
            thresholdOpt);

        // Root command
        var infileOpt = new Option<string>("--i");
        var outfileOpt = new Option<string>("--o");
        var rootCommand = new RootCommand("test") { infileOpt, outfileOpt, conversionCommand };
        rootCommand.SetHandler(
            (string i, string o) => { Console.WriteLine($"i={i}, o={o}"); },
            infileOpt, outfileOpt);

        return await rootCommand.InvokeAsync(args);
    }
}

Unexpected outputs:

> myapp --i infile.dat --o outfile.dat conversion --threshold 42

threshold=42

In the above I expect to see the value for the --i and --o options, as well as the threshold options associated with the conversion command, but the root command's handler isn't invoked.

Expected outputs:

> myapp --i infile.dat --o outfile.dat 

i=infile.dat, o=outfile.dat

> myapp conversion --threshold 42

threshold=42

The above are what I'd expect to see.

Dependencies: I'm using System.CommandLine 2.0.0-beta3.22114.1, System.CommandLine.NamingConventionBinder v2.0.0-beta3.22114.1, .net 6.0, and Visual Studio 17.1.3.

I'd be grateful for help in understanding what I'm doing wrong. Thanks.

Andy Johnson
  • 7,938
  • 4
  • 33
  • 50

1 Answers1

1

Based on the docs sample it seems only one verb gets executed. For example next:

var rootCommand = new RootCommand();
rootCommand.SetHandler(() => Console.WriteLine("root"));
var verbCommand = new Command("verb");
verbCommand.SetHandler(() => Console.WriteLine("verb"));
rootCommand.Add(verbCommand);
var childVerbCommand = new Command("childverb");
childVerbCommand.SetHandler(() => Console.WriteLine("childverb"));
verbCommand.Add(childVerbCommand);

return await rootCommand.InvokeAsync(args);

For no arguments will print root, for verb will print verb and for verb childverb will print childverb.

So if you need multiple actions performed it seems you will need to use another approach (for example manually processing rootCommand.Parse() result).

If you just want "--i" and "--o" accessible for conversion then add them to corresponding command:

// actually works without specifying infileOpt, outfileOpt on conversionCommand 
// but should be still present on the root one
// also rootCommand.AddGlobalOption can be a more valid approach
var conversionCommand = new Command("conversion") { thresholdOpt, infileOpt, outfileOpt}; 

// add here for handler
conversionCommand.SetHandler(
    (int threshold, string i, string o) => { Console.WriteLine($"threshold={threshold}i={i}, o={o}"); },
    thresholdOpt, infileOpt, outfileOpt);
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks! It's surprising to me that this doesn't seem to work. What I actually need is a number of general options that are always present and then one or more commands, each of which has its own set of distinct options. I'll investigate using rootCommand.Parse() but I suspect I'll have to do more work myself if I go down that route. – Andy Johnson May 16 '22 at 20:45
  • 1
    Something that could be considered is setting some options as global options. – Andrew McClement Jun 29 '22 at 14:34
  • 1
    @AndrewMcClement yes, I mention this in the code in the comments - _"also `rootCommand.AddGlobalOption` can be a more valid approach"_ – Guru Stron Jun 29 '22 at 14:51