Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can CommandHandler.Create() go more than 7 parameters? #458

Closed
lorenh opened this issue Mar 14, 2019 · 21 comments
Closed

Can CommandHandler.Create() go more than 7 parameters? #458

lorenh opened this issue Mar 14, 2019 · 21 comments
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@lorenh
Copy link

lorenh commented Mar 14, 2019

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?

@Keboo
Copy link
Member

Keboo commented Mar 14, 2019

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

@jonsequitur
Copy link
Contributor

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.

@jonsequitur jonsequitur added enhancement New feature or request help wanted Extra attention is needed good first issue Good for newcomers labels Mar 21, 2019
@AndyAyersMS
Copy link
Member

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

@jonsequitur
Copy link
Contributor

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; }
}

@AndyAyersMS
Copy link
Member

Thanks, that works nicely.

@granadacoder
Copy link

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

@granadacoder
Copy link

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.

@jonsequitur
Copy link
Contributor

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.

@granadacoder
Copy link

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

@jonsequitur
Copy link
Contributor

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

@granadacoder
Copy link

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;
    }

@granadacoder
Copy link

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

@jonsequitur
Copy link
Contributor

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.

@granadacoder
Copy link

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'

@granadacoder
Copy link

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.

@jonsequitur
Copy link
Contributor

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.

@granadacoder
Copy link

granadacoder commented May 8, 2020

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'

@jonsequitur
Copy link
Contributor

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" } );

@granadacoder
Copy link

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.

@granadacoder
Copy link

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.

@jonsequitur
Copy link
Contributor

You're welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

5 participants