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
Initial implementation of positional arguments #102
Conversation
765b373
to
8c708e7
Compare
@vsachs That's great, I appreciate you taking a stab at this issue. Could you please provide a few examples of how this would work on CLI and when would it fail (error)? Overall -- the change seems minimum invasive, so as long as the existing logic does not change (which it appears did not), and no big caveats with the logic I think it should be fine (considering we add some examples and test coverage for new code). One question though -- do you think with this approach it would be possible to make positional arguments not required? I imagine there may be use cases where positional arguments can be optional. Also there may be use cases where there may be a need for variable number of positional arguments (i.e. list of files to operate on, like |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Since you think it's a good approach I'll move forward with this:
I believe it would be possible to extend this approach to support both:
For now I'd like to complete this initial feature then we can evaluate whether to submit as-is or if we want to augment with additional functionality. Will try to be snappy about it so it doesn't get lost. |
This comment was marked as off-topic.
This comment was marked as off-topic.
@vsachs Please rebase the code onto master, so you get most up-to-date changes. There were some PRs merged yesterday fixing bugs related to sub-commands |
9d985bf
to
72a6431
Compare
72a6431
to
24b09dd
Compare
@vsachs Sorry for the delay. I've had some extra time over the weekend to look at this PR. As I said earlier -- it is fine as-is, I just think it potentially will be beneficial to separate positionals into their own methods rather than use If use The workaround for that, I think, would be to create a set of dedicated methods to add positional arguments. The benefits of this approach are:
It can still use functionality you added by proxy, but changing // note `short` and `long` not needed for positionals as they have no names on CLI
func (o *Command) StringPositional(opts *Options) *string {
opts.positional = true
... // something else
long := ... // generate some random string to ensure it does not overlap with any other argument long name
return o.String("", long, opts)
} What do you think about this approach? |
Should be a relatively mild refactor. Will get on that. |
30d4ae1
to
10d85f1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM if anyone cares :)
@vsachs something with the implementation that causes interesting failure, i've added a TestPos8 test to show it, within this test you can also move around Also I left a few comments with some minor things in current code. |
@akamensky I've poked at this and found two items, one I knew about and the other makes sense but I hadn't thought through: First Issue: The one I hadn't thought about.For the test you added, the result is not what you expected because the precedence of the commands controls which positional parses which value. As coded now the commands are parsed in LIFO order (deepest command first).
consequently the parse goes like this:
SolutionsChanging this would probably require a more substantial alteration to the parsing logic.
Second issue: Moving "-s strval" aroundThis can also be thought about as "altering the order in which positionals versus flags are added to the command".
In this scenario, where strval is added last, the positional arg logic causes cmd1pos1 to skip "-s" but consume "some string". This situation is complicated because "-s" may or may not have a default or be a boolean-flag. OptionsI see roughly three approaches:
|
@akamensky Sorry but I don't see your comments anywhere? Did github swallow them? |
argparse.go
Outdated
@@ -80,6 +80,11 @@ type Parser struct { | |||
// Options are specific options for every argument. They can be provided if necessary. | |||
// Possible fields are: | |||
// | |||
// Options.Positional - tells Parser that the argument is positional (implies Required) | |||
// Positional arguments do not require the flag name to precede them and must come in a specific order. | |||
// Positional sets Required=true, Default=nil, Shortname="" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, interesting, while Default == nil
makes sense (positional either provided or it is not), I am not so sure about Required == true
.
Assuming for a sub-command tree we defined 5 positionals: progname cmd1 cmd2 prognamePositional1 prognamePositional2 cmd1Positional1 cmd1Positional2 cmd2Positional
, and input provided on CLI progname cmd1 cmd2 somestr1 somestr2 somestr3
, the only way we can match positional arguments is in the order they provided. Hence:
prognamePositional1 == somestr1
prognamePositional2 == somestr2
cmd1Positional1 == somestr3
cmd1Positional2 == nil
cmd2Positional == nil
Which would make it possible to define "optional" positionals, as in -- if it was not provided, it is not set to any value, meantime any that provided will be matched to their corresponding positional in exactly same order (that is in example above prognamePositional1
should always be matched to somestr1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
progname cmd1 cmd2 prognamePositional1 prognamePositional2 cmd1Positional1 cmd1Positional2 cmd2Positional
----->
progname cmd1 cmd2 somestr1 somestr2 somestr3
----->
prognamePositional1 == somestr1
prognamePositional2 == somestr2
cmd1Positional1 == somestr3
cmd1Positional2 == nil
cmd2Positional == nil
I believe I can allow this relatively easily but it's going to open a can of worms. What if the only optional positionals are actually prognamePositional1 and prognamePositional2? We can't distinguish which positionals the user is trying to fill (unless we actually allow naming of the positionals on the CLI, which... basically defeats the whole point), so we will still end up with the same result above except that the last two are required and nil, giving us an error. Effectively they are all required in this scenario.
Do we want to require that any optional positionals be at the tail end of added arguments? If so this has the implication:
- That all sub-commands of any command with optionals can now only add optional positionals.
argparse_test.go
Outdated
com1 := parser.NewCommand("subcommand1", "beep") | ||
intval := com1.Int("i", "integer", nil) | ||
|
||
if err := parser.Parse(testArgs1); err != nil && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In its current form this test will give false result (test Pass) if there is an error, but error message is different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should fail in that case, I don't see how the current if statement could cause that problem. However I did realize it will pass if the error is nil which is definitely incorrect. Fixed that.
@vsachs Sorry, seems Github added new function to have all comments "pending" until review submitted. Should be visible now. |
I think 2nd option is the better. It also would make it possible to avoid positionals be always required (see one of my comments in-code above) |
Cool. I've implemented option 2 in commit [c1c45f5]. Do you have an opinion on the first issue I outlined above? The Parse Order of Operations problem? I outlined 5 approaches but the door is open to others, including writing a completely new algo for the positional parsing which occurs as a totally separate secondary stage progressing as (root->leaf, left->right) linearly. |
Been quite busy lately, perhaps I missed a few points and did not elaborate enough. I feel there is a decent amount of overlap between questions. So I will try to provide more detailed response below. On ordering issue
One of the goals of this library is/was to provide a very clear and simple way for devs to handle command line arguments. I think parsing in order of definition (see more below on this) should be clear and more intuitive approach. I had my mind more on a 2-stage parsing, where all "standard" arguments are parsed first, and then 2nd stage parses remaining elements as positionals. But how exactly this is implemented does not matter much as long as the goal of simple and intuitive use of this library is achieved. You already can probably see that the internal code of this library is quite messy, which is the result of trying 1-starting with simple functionality and adding more over time and 2-trying to keep the usage of this library as straight-forward as possible. There are a few other libs with very high flexibility and more functionality than this one, but using them is rather painful and amounts to writing 100s of lines of code for something that should be done in 5 minutes (since it does not contribute that much to business logic of application being written). In my opinion the most intuitive way to parsing positional arguments is in the order of their definition while following the order of command tree. That translates to: 1 - parent command positionals should precede sub-command positionals, 2 - within same command positionals should be parsed in order of their definition. To elaborate on that in more graphical terms:
I think above logic is rather straightforward and provides clear and easy to understand ordering rules. On required vs optionalI believe above logic for ordering also creates 2 ways to implement "optional" positionals:
An obvious issue with both is if application user provided some argument that does not exist (i.e. I am personally leaning towards 1st option here, because it is simple and easy to understand and should be rather easy to implement in this library. But please let me know if you see any other possible solutions to this issue, I may be missing some perspective here. On
|
On ordering issueOkay, I will attempt to achieve the proposed outcome, which you've outlined in the code block clearly. On required vs optionalOption 1 seems reasonable to me, at least in combination with the proposed parsing change related to the ordering implementation. List OptionalsI agree this is quite valuable. In our use case of the library we have a workaround to achieve this. I was planning to approach this in a PR for issue #22 and I think since this PR continues to expand in code scope, that remains my preference. |
…Default. Expose argument.Parsed
62871db
to
f965350
Compare
Parsing has been changed to the desired ordering and option 1 is utilized for positionals. Subsequently I've enabled defaults for positionals and added a GetParsed() function to the argument so that people can detect if a positional was detected or not. |
Hm, I can see how this logic somehow overlaps. This perhaps could be done through nargs functionality then if it will be added. |
@vsachs since you've invested into this PR so much time and effort (and since I have limited time to work on this lib), I have invited you to collaborators. Hence you can merge this PR whenever. Please note that I have changed default branch from |
Appreciated! I'll do a bit more local testing and merge it in.
Sounds good! |
Awesome! Really appreciate your work on this issue. I think with this new feature can cut new release for v1.4.0. |
This PR concerns #20 (Positional Arguments issue)
This PR is solely to provide a prototype for a grounded discussion on implementation of positional arguments.
@akamensky I am eager to hear your thoughts on this methodology for positional arguments. Let me know what you think!