Command-line-api: Can CommandHandler.Create() go more than 7 parameters?

Created on 14 Mar 2019  路  21Comments  路  Source: dotnet/command-line-api

This works:
subcommand.Handler = CommandHandler.Create((int p1, int p2, int p3, ... int p7) => ...

This doesn't:
subcommand.Handler = CommandHandler.Create((int p1, int p2, int p3, ... int p8) => ...

Is it reasonable to ask to keep going a few more parameters since Func<> and Action<> support more?

enhancement good first issue help wanted

Most helpful comment

Another approach that's worth considering: You can do bind complex objects.

command.Handler = CommandHandler.Create<MyType>(obj => /*   */ );

Constructor injection and property setters are both supported, with the same naming conventions as we use to match parameters.

All 21 comments

My personal opinion is the CommandHandler.Create should probably include overloads to match the Func<> and Action<> arity.

Another approach that's worth considering: You can do bind complex objects.

command.Handler = CommandHandler.Create<MyType>(obj => /*   */ );

Constructor injection and property setters are both supported, with the same naming conventions as we use to match parameters.

Can you say a bit more about this? I have a command with 9 options ... what should I do?

Let's assume you have a command line with options -a through -i. You can create a handler like this:

command.Handler = CommandHandler.Create<MyType>(myType => /*   */ );

where MyType looks like either of the following:

public class MyType
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
    public string D { get; set; }
    public string E { get; set; }
    public string F { get; set; }
    public string G { get; set; }
    public string H { get; set; }
    public string I { get; set; }
}

// or...

public class MyType
{
    public MyType(string a, string b, string c, string d, string e, string f, string g, string h, string i)
    {
        A = a;
        B = b;
        C = c;
        D = d;
        E = e;
        F = f;
        G = g;
        H = h;
        I = i;
    }

    public string A { get; }
    public string B { get; }
    public string C { get; }
    public string D { get; }
    public string E { get; }
    public string F { get; }
    public string G { get; }
    public string H { get; }
    public string I { get; }
}

Thanks, that works nicely.

My personal opinion is the CommandHandler.Create should probably include overloads to match the Func<> and Action<> arity.

I agree.

Aka, 16.

https://docs.microsoft.com/en-us/dotnet/api/system.func-17?view=netcore-3.1

Let's assume you have a command line with options -a through -i. You can create a handler like this:

command.Handler = CommandHandler.Create<MyType>(myType => /*   */ );

where MyType looks like either of the following:

public class MyType
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
    public string D { get; set; }
    public string E { get; set; }
    public string F { get; set; }
    public string G { get; set; }
    public string H { get; set; }
    public string I { get; set; }
}

// or...

public class MyType
{
    public MyType(string a, string b, string c, string d, string e, string f, string g, string h, string i)
    {
        A = a;
        B = b;
        C = c;
        D = d;
        E = e;
        F = f;
        G = g;
        H = h;
        I = i;
    }

    public string A { get; }
    public string B { get; }
    public string C { get; }
    public string D { get; }
    public string E { get; }
    public string F { get; }
    public string G { get; }
    public string H { get; }
    public string I { get; }
}

Can you show the

my.exe whatIsHere

for this?

Thanks.

This would work for the example:

> my.exe -a -b -c -d -e -f -g -h -i

Name matching is by parameter name for simple types, and by constructor or property names for more complex types.

I'm confused between what is the "flag", and what is the value.

Can you repost with values like

valuea valueb (etc, etc)

is the below valid for using "MyType" ?

my.exe -a valuea -b valueb -c valuec -d valued -e valuee -f valuef -g valueg -h valueh -i valuei

Sorry, what I posted would be valid for bools. Your example is correct.

I appreciate your quick responses.

I am currently getting:

Unrecognized command or argument 'valuea'
Unrecognized command or argument '-b'
Unrecognized command or argument 'valueb'
Unrecognized command or argument '-c'
Unrecognized command or argument 'valuec'
Unrecognized command or argument '-d'
Unrecognized command or argument 'valued'
Unrecognized command or argument '-e'
Unrecognized command or argument 'valuee'
Unrecognized command or argument '-f'
Unrecognized command or argument 'valuef'
Unrecognized command or argument '-g'
Unrecognized command or argument 'valueg'
Unrecognized command or argument '-h'
Unrecognized command or argument 'valueh'
Unrecognized command or argument '-i'
Unrecognized command or argument 'valuei'

mycommandone:
mycommandone_description

Usage:
MyExe mycommandone [options]

Options:
--mytypeone mytypeone_description

==============================
The "args" that I am passing in

    myexe.exe mycommandone --mytypeone -a valuea -b valueb -c valuec -d valued -e valuee -f valuef -g valueg -h valueh -i valuei

The code for setup:

    public Command CreateCommand()
    {
        var cmd = new Command("mycommandone", "mycommandone_description");

        cmd.AddOption(
            new Option("--mytypeone", "mytypeone_description")
            {
                Argument = new Argument<MyType>() { Arity = ArgumentArity.ExactlyOne },
                Required = true
            });

        cmd.Handler = CommandHandler.Create<MyType>((MyType myTypeInstance) =>
        {
            try
            {
                Console.WriteLine("the value of A is '{0}'", myTypeInstance.A);
                Console.WriteLine("the value of B is '{0}'", myTypeInstance.B);
                Console.WriteLine("the value of C is '{0}'", myTypeInstance.C);
                Console.WriteLine("the value of D is '{0}'", myTypeInstance.D);
                Console.WriteLine("the value of E is '{0}'", myTypeInstance.E);
                Console.WriteLine("the value of F is '{0}'", myTypeInstance.F);
                Console.WriteLine("the value of G is '{0}'", myTypeInstance.G);
                Console.WriteLine("the value of H is '{0}'", myTypeInstance.H);
                Console.WriteLine("the value of I is '{0}'", myTypeInstance.I);



                return 0;
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, ex.Message);
                return 999; /* ExceptionExitCode; */
            }
        });

        return cmd;
    }

I tried this as well:

MyExe.exe mycommandone mytypeone -a valuea -b valueb -c valuec -d valued -e valuee -f valuef -g valueg -h valueh -i valuei

===========

    public Command CreateCommand()
    {
        var cmd = new Command("mycommandone", "mycommandone_description");

        cmd.AddCommand(new Command("--mytypeone", "mytypeone_description"));

        cmd.Handler = CommandHandler.Create<MyType>((MyType myTypeInstance) =>
        {
            try
            {
                Console.WriteLine("the value of A is '{0}'", myTypeInstance.A);
                Console.WriteLine("the value of B is '{0}'", myTypeInstance.B);
                Console.WriteLine("the value of C is '{0}'", myTypeInstance.C);
                Console.WriteLine("the value of D is '{0}'", myTypeInstance.D);
                Console.WriteLine("the value of E is '{0}'", myTypeInstance.E);
                Console.WriteLine("the value of F is '{0}'", myTypeInstance.F);
                Console.WriteLine("the value of G is '{0}'", myTypeInstance.G);
                Console.WriteLine("the value of H is '{0}'", myTypeInstance.H);
                Console.WriteLine("the value of I is '{0}'", myTypeInstance.I);

                return 0;
            }
            catch (Exception ex)
            {
                string extraMsg = string.Format(ErrorMessageSwallowingExceptionAndReturningCode, ExceptionExitCode);
                this.logger.Log(new LogEntry(LoggingEventTypeEnum.Error, extraMsg, ex));
                return ExceptionExitCode;
            }
        });

        return cmd;
    }

=================================

==============================================

'mytypeone' was not matched. Did you mean 'mytypeone'? << may want to look into this separate from my issue(s) :)

Unrecognized command or argument 'mytypeone'
Unrecognized command or argument '-a'
Unrecognized command or argument 'valuea'
Unrecognized command or argument '-b'
Unrecognized command or argument 'valueb'
Unrecognized command or argument '-c'
Unrecognized command or argument 'valuec'
Unrecognized command or argument '-d'
Unrecognized command or argument 'valued'
Unrecognized command or argument '-e'
Unrecognized command or argument 'valuee'
Unrecognized command or argument '-f'
Unrecognized command or argument 'valuef'
Unrecognized command or argument '-g'
Unrecognized command or argument 'valueg'
Unrecognized command or argument '-h'
Unrecognized command or argument 'valueh'
Unrecognized command or argument '-i'
Unrecognized command or argument 'valuei'

mycommandone:
mycommandone_description

Usage:
Surescripts.Direct.Provisioning.AdminCommandLineInterface mycommandone [command]

Commands:
--mytypeone mytypeone_description

When binding to a complex type you still need to define the options. Here's a full working example:

image

Note that I changed the invoked command to RootCommand and attached the handler to the inner command ("mytypeone").

Also note that if you use the MyType variant that has constructor parameters and no setters, then the incomplete command line will result in null being passed to the handler, since there was no way to pass all of the required arguments. Making the constructor arguments optional will get you an instance, though.

I appreciate your help.

I am still getting issues.

I've coded up a clean-sample here:

https://github.com/granadacoder/system-command-line-demo/tree/feature/InitialCommit

If you get a chance to look, I would be grateful. Thank you.

README has enough notes to get going.

I coded it to your example...."MyTypeCommandCreator.cs".

But still getting:

Required command was not provided.
Unrecognized command or argument '--mytypeone'
Unrecognized command or argument '-a'
Unrecognized command or argument 'valuea'
Unrecognized command or argument '-b'
Unrecognized command or argument 'valueb'
Unrecognized command or argument '-c'
Unrecognized command or argument 'valuec'
Unrecognized command or argument '-d'
Unrecognized command or argument 'valued'
Unrecognized command or argument '-e'
Unrecognized command or argument 'valuee'
Unrecognized command or argument '-f'
Unrecognized command or argument 'valuef'
Unrecognized command or argument '-g'
Unrecognized command or argument 'valueg'
Unrecognized command or argument '-h'
Unrecognized command or argument 'valueh'
Unrecognized command or argument '-i'
Unrecognized command or argument 'valuei'

Ok,

My RunJustRootCommandDemo does work... if I send this in. (note the no prefix "--" on the first argument)

mytypeone -a valuea -b valueb -c valuec -d valued -e valuee -f valuef -g valueg -h valueh -i valuei

But check out my "RunParserDemo" method.

Basically, if the RootCommand gets pushed to a Parser, the code fails. This may be intentional..just highlighting it.

        CommandLineBuilder clb = new CommandLineBuilder();

            RootCommand cmd = /* from MyTypeCommandCreator */
            this.logger.LogInformation(string.Format(LogMessageAddingICommandCreator, cmd.Description));
            clb.AddCommand(cmd);

        Parser prsr = clb
            .UseDefaults()
            .Build();

        return prsr;

All of this code in the github repo above.

I see that you're using an old version, System.CommandLine.Experimental. Give it a try with the latest here: https://www.nuget.org/packages/System.CommandLine/.

This probably is not related to any issues you've described, though.

Gaaaaaaaaaaaa! I updated the nuget package and pushed the code. ( I did not realize I was using an experimental outdated version..Thanks.)

https://www.nuget.org/packages/System.CommandLine/

<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20253.1" />

Short Version:

This is WORKING !!

mytypeone -a valuea -b valueb -c valuec -d valued -e valuee -f valuef -g valueg -h valueh -i valuei

Not working is adding it to a "Parser". In case you're interested.

Working Output:


parameter count = 19
Arg[0] = [mytypeone]
Arg[1] = [-a]
Arg[2] = [valuea]
Arg[3] = [-b]
Arg[4] = [valueb]
Arg[5] = [-c]
Arg[6] = [valuec]
Arg[7] = [-d]
Arg[8] = [valued]
Arg[9] = [-e]
Arg[10] = [valuee]
Arg[11] = [-f]
Arg[12] = [valuef]
Arg[13] = [-g]
Arg[14] = [valueg]
Arg[15] = [-h]
Arg[16] = [valueh]
Arg[17] = [-i]
Arg[18] = [valuei]


cmd.GetType='RootCommand'
the value of A is 'valuea'
the value of B is 'valueb'
the value of C is 'valuec'
the value of D is 'valued'
the value of E is 'valuee'
the value of F is 'valuef'
the value of G is 'valueg'
the value of H is 'valueh'
the value of I is 'valuei'
RootCommand.InvokeAsync='0'

The root command isn't intended to be supplied on the command line, though the parser does understand it so that you can, for example, parse a complete, un-split string.

So, for example, for input like this:

> my.exe subcommand1 --option1 argument

Main will get called with a string[] args of new[] { "subcommand1", "--option1", "argument" }. Arg zero, i.e. "my.exe", is not passed to Main.

But System.CommandLine lets you do any of the following with equivalent results:

parser.Parse("subcommand1 --option1 argument");
parser.Parse("my.exe subcommand1 --option1 argument");
parser.Parse(new [] { "subcommand1", "--option1", "argument" } );
parser.Parse(new [] { "my.exe","subcommand1","--option1","argument" } );

I finally figured it out. I am able to have multiple Commands off the (vanilla) RootCommand now. I was (errantly) using the Parser when I should have been using the RootCommand. I'll update my sample when I get a chance.

Thanks for your quick feedback.

Ok, I've updated the sample.

https://github.com/granadacoder/system-command-line-demo

This shows strong binding to a simple POCO (two different Poco's)....with 2 "forks" for the functionality.

Basically, this:

         example one
         MyCompany.MyExamples.SystemCommandLineOne.ConsoleOne.exe mytypeone -a valuea -b valueb -c valuec -d valued -e valuee -f valuef -g valueg -h valueh -i valuei

        example two
        MyCompany.MyExamples.SystemCommandLineOne.ConsoleOne.exe showdatetime --includedate false --dateformat "MM/dd/yyyy" --includetime

I'm "good" now. Thanks for the help.

You're welcome!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mariopasquali picture mariopasquali  路  4Comments

jonsequitur picture jonsequitur  路  4Comments

adamsitnik picture adamsitnik  路  5Comments

AlgorithmsAreCool picture AlgorithmsAreCool  路  6Comments

anreton picture anreton  路  5Comments