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

Interactivity & Parent-Child Commands #654

Open
hiadusum opened this issue Jun 24, 2020 · 8 comments
Open

Interactivity & Parent-Child Commands #654

hiadusum opened this issue Jun 24, 2020 · 8 comments
Labels

Comments

@hiadusum
Copy link

I want to build a command line where the user should be able to run a parent command like 1. And the commands 2 and 3 should also have access to argument values of command 1.

  1. GetAnimal -Name "a" -Age "b"
  2. GetDog -Breed "c"
  3. GetCat -Color "d"

I was wondering if commandline supports interactivity (users should be able to run multiple child commands after running the parent command until they exit), and parent-child structure described above.

@moh-hassan
Copy link
Collaborator

You can build your own keyboard input, split argument to args[] and the pass args[] to to the parser. Loop until user exit;

@hiadusum
Copy link
Author

Can you please explain more as to what you mean by building own keyboard input?

@moh-hassan
Copy link
Collaborator

what you mean by building own keyboard input

Something like:

string ReadKeyBoard()
{
//build your own keyboard handling to get commandline arguments, here simple ReadLine
  Console.WriteLine("Enter arguments, type exit to Exit");  
  var argument= Console.ReadLine();  //read keyboard  
  return argument;
}

To keep the values of Options after each argument entry in the loop until user exit, use the parser Factory Method:

public ParserResult<T> ParseArguments<T>(Func<T> factory, IEnumerable<string> args)

How to use the factory method:

//create instance of Options, you can set initial values
var myOptions = new Options (); 

while (!exit)
{
    var args=  ReadKeyBoard().Split();
    // check if user exit and break
   //....
   //Parse arguments, Parser keep the values of options and it's re-evaluated per new args
     Parser.Default.ParseArguments(()=>options, args) 	  
	       .WithParsed(Run);
}

@hiadusum
Copy link
Author

Thanks you so much for providing the above detailed explanation!

I found this example in the docs, and I am trying to see how I can modify this for my requirements. Can you please explain how to fit what you have explained into this example?

[Verb("add", HelpText = "Add file contents to the index.")]
class AddOptions {
  //normal options here
}
[Verb("commit", HelpText = "Record changes to the repository.")]
class CommitOptions {
  //commit options here
}
[Verb("clone", HelpText = "Clone a repository into a new directory.")]
class CloneOptions {
  //clone options here
}

int Main(string[] args) {
  return CommandLine.Parser.Default.ParseArguments<AddOptions, CommitOptions, CloneOptions>(args)
	.MapResult(
	  (AddOptions opts) => RunAddAndReturnExitCode(opts),
	  (CommitOptions opts) => RunCommitAndReturnExitCode(opts),
	  (CloneOptions opts) => RunCloneAndReturnExitCode(opts),
	  errs => 1);
}

@moh-hassan
Copy link
Collaborator

moh-hassan commented Jun 29, 2020

Parser Factory method is applied only on non-verb options.
Verb classes can't share values (they are passed as Types[]).
Can you show me your option class(s).

@hiadusum
Copy link
Author

hiadusum commented Jun 30, 2020

The following are my options' classes. Like I have mentioned earlier, I want the user to always run Connect command (parent command) before they run any of the three other commands.

In your explanation, you have add the below code. However, I am a bit confused as to what it represents since I have multiple options classes.

//create instance of Options, you can set initial values
var myOptions = new Options (); 
[Verb("connect", HelpText = "Connect.")]
class ConnectOptions {
  [Option('sn', "service-name", Required = true, HelpText = "Service Name.")]
  public string ServiceName { get; set; }

  [Option('pid', "partition-id", Required = true, HelpText = "Partition Id.")]
  public string PartitionId { get; set; }
}

[Verb("getcollection", HelpText = "Get Collections.")]
class GetCollectionOptions {
  // do not take any other additional arguments besides service name and partition id that are present on Connect class
}

[Verb("getitem", HelpText = "Get Item.")]
class GetItemOptions {
  [Option('cn', "collection-name", Required = true, HelpText = "Collection Name.")]
  public string CollectionName { get; set; }

  [Option('k', "key", Required = true, HelpText = "Key.")]
  public string Key { get; set; }
}

[Verb("getitems", HelpText = "Get Items.")]
class GetItemsOptions {
  [Option('cn', "collection-name", Required = true, HelpText = "Collection Name.")]
  public string CollectionName { get; set; }

  [Option('fi', "firstitem", Required = true, HelpText = "First Item.")]
  public string FirstItem { get; set; }

  [Option('c', "count", Required = true, HelpText = "Count.")]
  public int Count { get; set; }
}

@moh-hassan
Copy link
Collaborator

Let us define the problem: you need to enforce the user to run verbs in some order based on some business logic.
It's not related to the parent/child concept. It's a business logic problem.
Every verb is Isolated Entity, but from within your application, you can share its values for reusing.

I want the user to always run Connect command (parent command) before they run any of the three other commands.

Do this check in your Run method, and share its values, show him error and let user try again.

The next code simplify the problem

    //declare shared verbs
    ConnectOptions ConnectionVerb;
    GetCollectionOptions GetCollectionVerb;
    GetItemOptions GetItemVerb;
    GetItemsOptions GetItemsVerb;

    public void Run()
    {
        var exit = false;       
        while (!exit)
        {
            var argument = ReadKeyBoard();
            if (argument.Equals("exit"))
                break;
            var args = argument.Split();            
            
            Parser.Default.ParseArguments<ConnectOptions,GetCollectionOptions,GetItemOptions,GetItemsOptions>(args)
                .WithParsed((ConnectOptions opts)            => Run(opts))
                .WithParsed((GetCollectionOptions opts)     => Run(opts))
                .WithParsed((GetItemOptions opts)            => Run(opts))
                .WithParsed((GetItemsOptions opts)           => Run(opts))
                .WithNotParsed(errs=>HandleError(errs);

        }
        Console.WriteLine("End of application)");
    }	
  void Run(ConnectOptions opts)
    {          
          ConnectionVerb = opts;//share the verb
        //do connection stuff ...
    }
    void Run(GetCollectionOptions opts)
    {         
        // check if ConnectionVerb is set by a previous command
        if (ConnectionVerb == null)
        {
            ShowWarning("you should run: connect <options> first, try again");
            return;
        }        
        GetCollectionVerb = opts;//share the verb
      //reuse the ConnectionVerb properties, it's available from a previous run.
        //do stuff ...
    }
	
   //the same with other verbs
   

@hiadusum
Copy link
Author

hiadusum commented Jul 2, 2020

@moh-hassan Thank you so much! It was very helpful. Will try this out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants