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

Determine order of NamedArguments #49

Open
RedMser opened this issue Jul 27, 2017 · 6 comments
Open

Determine order of NamedArguments #49

RedMser opened this issue Jul 27, 2017 · 6 comments

Comments

@RedMser
Copy link

RedMser commented Jul 27, 2017

Unless I misunderstood the differences between PositionalArguments and NamedArguments, you can currently not have a NamedArgument and determine its position in the list of command line arguments.

This would be useful as you could allow different commands to be executed in a certain order without being restricted by the Verbs system (e.g. prog.exe --load-config default.cfg --open-file test.txt).
Similar use case is shown in FFMPEG's argument system, where you refer to input media files by their order in the argument list, or certain flags only apply to the previous input.

Although untested, this may be worked around by implementing setters of the argument's properties which then increment a counter for determining the order. In that case, the parsing order will have to be clearly defined.

Thanks for your hard work,
RedMser

@nemec
Copy link
Owner

nemec commented Jul 27, 2017

Can you give a specific example of what you are trying to do? I understand what you mean with ffmpeg, but I am having a bit of trouble understanding the problem when mixed with verbs.

Arguments are completely independent from verbs, and the program and each verb has its own set of independent arguments, like below, and verbs should not prevent you from implementing named arguments. They do restrict positional arguments in the main program, but positional arguments will then work within the verb.

prog.exe --load-config default.cfg --open-file test.txt commit -m "some commit message"
         |________________app_arguments_______________|        |____verb arguments____|

From what I can gather about your question, you may want to explore the ParseAction.Append action, which lets you combine multiple of the same arguments into a list. You can then iterate through them and correlate order by index in the list. Something like this:

void Main()
{
	var args = "-f mp4 -w1024 -l768 -f wav -w 512 -l 512".Split();
	var opt = CliParser.Parse<Options>(args);
	

	if (opt.Filetype.Count != opt.Width.Count || opt.Filetype.Count != opt.Length.Count)
	{
		throw new Exception("Must provide same number of parameters for each");
	}

	foreach (var tup in opt.Filetype
		.Zip(opt.Width, (f, w) => new { f, w })
		.Zip(opt.Length, (fw, l) => new { Filetype = fw.f, Width = fw.w, Length= l }))
	{
		Console.WriteLine("{0}: {1}x{2}", tup.Filetype, tup.Width, tup.Length);
	}
}

public class Options
{
	[NamedArgument('f', Action=ParseAction.Append)]
	public List<string> Filetype { get; set; }

	[NamedArgument('w', Action=ParseAction.Append)]
	public List<int> Width { get; set; }
	
	[NamedArgument('l', Action=ParseAction.Append)]
	public List<int> Length { get; set; }
}

@RedMser
Copy link
Author

RedMser commented Jul 27, 2017

Thanks for taking your time @nemec !

In my case, I have a program where I would want the command line to function not only as a way of specifying settings for the program, but to also be able to execute functions. These need to be called in the order that they are specified, since each flag would manipulate data in certain ways.

The system you proposed would definitely work in certain cases, but only for being able to detect the order of the same command being used multiple times, which would limit what I am trying to accomplish.

Verbs don't directly have any role in this situation. I only looked up on what different data structures existed in clipr and they all do not seem to be a solution to this problem.

So here's a hopefully more comprehensible example of what I am trying to achieve:

// Initialize arguments - these should be substitutable with the short variants as well
var args1 = "--foo --bar 5 --function";
var args2 = "--bar 3 --function --foo";

// Example parser (could parse args2 as well)
var parser = new CliParser<Options>(new Options());
parser.Parse(args1);

// Executing functions based on their order
foreach (var flag in parser.Arguments)
{
    switch (flag.Name)
    {
        case "foo":
            Console.WriteLine("foo");
            break;
        case "bar":
            Console.WriteLine("bar" + flag.Value.ToString());
            break;
        case "function":
            Console.WriteLine("function");
            break;
    }
}

// Output for args1:
// foo
// bar5
// function

// Output for args2:
// bar3
// function
// foo

Obviously, above code would not compile. The Options class would need argument definitions which work the way they usually do (for --function for example, it could store a value true or a Const value).

Essentially I would want to be able to go through the list of NamedArguments as they were specified, and optionally get the value as it was specified directly from the argument instance (otherwise, it would suffice getting it from the respective property from the Options instance).

Again, thanks for your help and for this convenient library! 😄

@nemec
Copy link
Owner

nemec commented Jul 28, 2017

Thanks for providing more details, that's a very interesting idea. I will give some thought to what it would take to implement this in the code.

@nemec
Copy link
Owner

nemec commented Dec 22, 2017

Hey @RedMser I am taking a look at this now and am considering adding an event that is triggered immediately after a parameter and its values are parsed. You will have to manage what exactly happens (function execute, print, etc.) within the handler, but the handlers will be required to execute in left-to-right parsing order within an object.

For the event data, I am considering invoking the event with an EventArgs that contains the option property name and a the value of the parsed argument. Note that given the below options class, store.Name will be FooThing rather than foo or --foo - there will be no way to determine whether the user said -f 5 or --foo 5 in the command line, but IMO that should be enough.

class Options
{
    [NamedArgument('f', 'foo')]
    public int FooThing { get; set; }
}

From your end it will look something like this:

var args1 = "--foo --bar 5 --function";
var args2 = "--bar 3 --function --foo";

Action<object, ParsedEventArg> act = (sender, store) => {
	switch (store.Name)
	{
		case "FooThing":
			Console.WriteLine("foo");
			break;
		case "BarThing":
			Console.WriteLine("bar" + store.Value.ToString());
			break;
		case "Function":
			Console.WriteLine("function");
			break;
	}
};

var popts = new ParserOptions();
popts.OnParse += act;
var parser = new CliParser<Options>(new Options(), ParserOptions=popts);
parser.Parse(args1);

Thoughts?

@RedMser
Copy link
Author

RedMser commented Dec 23, 2017

Seems like a solid way to solve the problem!

Retrieving the exact name of the flag entered by the user indeed is nothing really required, since both the short and long names are supposed to be synonyms anyway.
I'd need to mess with it in an implementation to really see anything missing from the event system (or the EventArgs' members).

@nemec nemec added this to the 2.0 Release milestone Feb 3, 2018
@nemec
Copy link
Owner

nemec commented Feb 3, 2018

Hey @RedMser I just checked in an implementation of my solution described above. I am putting it in the 2.0 branch, so I have no idea when it will be officially released, but once I update the codebase for a newer .NET Core I will probably release a beta version on NuGet.

Let me know if you have any other suggestions, or if I missed something.

public static void ParseArgsWithEvent()
{
var cfg = new ParserOptions();
cfg.OnParseArgument += (ctx, args) =>
{
switch (args.ArgumentName)
{
case "Foo":
Console.WriteLine("foo");
break;
case "Bar":
Console.WriteLine("bar " + args.Value.ToString());
break;
case "Function":
Console.WriteLine("function");
break;
}
};
var parser = new CliParser<OrderedArgs>(cfg);
parser.Parse("--foo --bar 5 --function".Split(), new OrderedArgs());
Console.WriteLine("--- Next ---");
parser.Parse("--bar 3 --function --foo".Split(), new OrderedArgs());
}

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

No branches or pull requests

2 participants