-
Notifications
You must be signed in to change notification settings - Fork 2k
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
implement fish_getopt
(or argparse
) command
#4190
Comments
See src/builtin_history.cpp for an example of where we have long options with no short option analog available to the user. For example, |
See also issue #3417 opened nine months ago and closed because it was believed a DocOpt based solution would be implemented three months ago if not earlier. And the original idea is now 4.6 years old. I love the idea of a more human friendly syntax for specifying how command arguments are processed. But it doesn't appear that a "DocOpt" implementation is in our near future. Whereas a builtin based on our |
The design in general looks solid. I especially like integrating it directly with the shell, saving stuff in variables. One thing I don't quite like is how prominent the short options are. For one, I'd prefer if the variable names used the long version (if there is one, of course). Also,
This desperately needs "--" as a separator, since it's about parsing options. So what I'd suggest is actually just to make "--" mandatory. Every argument before it is a flag specification, and every one after it is an argument to check. That would remove the need for "-o". |
That's okay by me but I'm ambivalent. I was using the short flag for consistency with how we use
Agreed, 100%. And no bundling of the option specs by separating them by commas. That was simply a nod to how the
|
Regarding using the short or long name when creating the Also, if a flag can only occur once we'll actually let it be used more than once but only the last value seen will be saved. We'll also support a |
Of all the option parsing code in the world, the Python argparse module has always struck me as having the best design. getopt's weird DSL is impenetrable; although the proposed syntax is better, I wonder if we could make this more descriptive. What about using the same sort of options that |
Well, I already have a working implementation -- in under 400 lines of code. What I've outlined here is similar in flavor to Python's argparse. We obviously can't do it the way that module does because fish script doesn't support OOP patterns. I started writing a man page last night. I'm going to rewrite the two functions ( |
If we wanted to do both, we could add a fish_getopt --options (fish_opt --short h --long help [--long-only] [--required] [--multi]) (fish_opt --short l --long list ...) ... The |
Not a fan of that idea, @zanchey. While we don't want fish script to be as terse as a typical Perl program I think the level of verbosity you're proposing actually hinders comprehension once you have more than one option. Take a look at the PR I opened which includes a couple of functions converted to the new |
Having said that I'm not opposed to implementing a Also, I think this pattern is preferable to trying and put it all on one command line:
|
fish_getopt
functionfish_getopt
(or argparse
) command
I implemented a |
@faho raised a couple of points in this comment on PR #4204 that are better addressed here.
I guess the question is how often does this pattern occur in fish functions? Obviously it does occur. For example, you'll also find it in the I propose We could also add a On a related note it might be nice to have a way to validate the value associated with a flag. We often want to ensure a value is a valid int within a certain range of values or is a string from a finite set of values. Including a function name in the option spec is the obvious solution. The function is called when the flag value is parsed and it returns zero if the value is valid else one. We would provide a few functions to handle common cases such as validating the value is an int. The user is free to create functions for more complex validation.
The solution here is to use the
P.S., The |
Adding support for a |
Yes, sorry!
Yes, that'll work.
The obvious drawback of that is that we only have global functions. So if you just need to validate one thing you're still polluting the global namespace. It might be possible to allow arbitrary code that will just be called with the value as an additional argument (as if called as So you'd do Anyway, this doesn't need to be included now.
Ah, so "--require-order" requires the order of "options before non-option arguments"? That should be renamed since it's not intuitive that it could also be used for this purpose, but I can't come up with a great name either. "--until-first-nonoption"? "--options-before-arguments"?
What does that do? I can't find it in the current code. |
The The
Most commands have (or should have) an explicit check that they were handed no non-flag arguments, or exactly one, or at least one, etc. So As for flag value validation that will definitely not be part of this change. I'll open a new issue to discuss how that feature might work. |
@ridiculousfish, or anyone else, if you want to object to this please do so immediately. Otherwise I'm going to merge this in the next 48 hours. Just as soon as I finish the documentation and add more unit tests. I would be happy to see a DocOpt based solution become our standard for both builtin and fish script commands. However, that doesn't appear likely in the near future given that #478 has been open almost five years. Furthermore, it is unclear that a DocOpt based solution will maintain compatibility with current argument parsing behavior. Practicality trumps purity. My solution obviously maintains compatibility with existing behavior for builtin commands since it does not change how builtin commands parse their arguments. It can change how fish script commands parse their arguments but only if they are modified to use this new builtin. And even then the change in behavior is almost certainly for the better by being more consistent with how builtin commands parse their arguments. |
"--stop-at-nonoption"?
Nice! |
I've been waiting on the DocOpt for quite some time and agree that this is a realistic way forward. But I found the syntax unclear. It would be better to make it more like how the flags are used, and us common glob patterns for bool/single/multi: fish_getopt 'h/help?' 'v/verbose*' 'n/name=' 'o/output=+' -- $argv
fish_getopt 'h/help?' '{add,commit}=subcommand' -- $argv
# Or just use $argv[1] instead of the =subcommand above
switch $subcommand
case add:
fish_getopt 'p/patch' -- $argv
case commit
fish_getopt 'a/all' -- $argv
end
fish_getopt 'x' 'y?' 'z=' -- $argv |
Ooh, I like that idea! So, with that we have these multiplicities:
If something takes an argument, you add a "=" to the spec - "output=" takes an argument. If the argument is optional, you use "=?". "*" and "+" don't really make sense here. So "o/output+=?" would mean at least one output option with an optional argument. I like this syntax much more, but I see two issues:
|
Yes, that's what I meant. Also since you got it right it fits right in with the least surprise principle.
Yeah, but I'm just gonna assume it's common enough to be fine for now. One way would be to default boolean flags to optional (which kinda already are) and just use ? for flags with arguments.
I would expect the empty string, or perhaps 0. If you use the empty string though it would simplify the flags with arguments specifier.
But it looks really funky, and reversing them looks even funkier "~+="/"+~=". Another idea is to use parentheses "(n/name=)+" "n/name=?" "(n/name=?)+" |
I think that would be even more surprising. Another possibility is to not offer mandatory-once here at all. It's not all that common (mostly it's used for options that are really subcommands, e.g. "o/output" would be an optional boolean, and "o/output=" would be an optional value. "o/output=?" would be an optional flag with an optional argument. "o/output*" would be an optional boolean that can be given more than once, and "o/output*=" would be a flag that can be given more than once, but needs a value every time. Come to think of it, "at least once" is also rarely used. I can't come up with any examples off the top of my head.
I can't recall any command using that. Traditionally,
What does "o/output*=" mean then? I imagine "o/output*=?" means the flag can be given zero to infinity times and each occurence can have an argument. "o/output*=" means the flag can be given zero to infinity times and each one must have an argument. |
Sounds good, all the required stuff could be solved with actual flags to |
Yeah, this is a deal breaker. Also, the default (no special modifier chars after the flag name) has to be a simple boolean flag since that is far and away the most common use case. I think much of this alternative syntax is pushing too much logic into the argparse implementation. Keep in mind that the primary goal is 100% compatibility with I don't have any objection to
The |
Yes subcommands is far off but I in favour of thinking a ahead a bit to try to avoid shooting oneself in the foot. |
@maxnordlund, See this comment for how subcommands can be handled in a straightforward manner using the current design. Keep in mind too that fish script that has subcommands is rare. So absent a compelling argument it doesn't make sense to implement support for it in |
Yes I read that earlier, my thinking was to use the |
This implements a `fish_opt` command that provides a way for people to create option specs for the `argparse` command as an alternative to creating such strings by hand. Fixes #4190
Convert our two functions that use `getopt` to use our new `argparse` builtin. Fixes #4190
This implements some unit tests for the new `argparse` command and fixes a couple of bugs those tests brought to light. Fixes #4190
I decided earlier to implement an alternative mechanism for specifying mutually exclusive flags: the Now that I have merged my implementation for the |
I really like this new feature! I was waiting for something like this for ages now, so thanks for providing something like this! One question though: is there a nice way to define a default value for a variable if a flag is not set? At least I do not see anything as part of Best regards |
@wilriker: As always, Note that, because of the way local scope currently works, you need to use |
Nine months ago I asked if we should implement a fish version of the
getopt
command. At the time I was assured we would have a DocOpt solution by now. It's not clear that a DocOpt based solution will be implemented in the near future. And after recently looking at several of our functions that do option parsing I can no longer live with the current state of affairs. For parity with builtin commands we need a way to usewgetopt_long()
from fish script.We most definitely do not want to model our solution on the GNU
getopt
command. Not least of which because it requires eval'ing the output and eval is evil. Nor do I care for the bash/zshgetopts
command. It makes option parsing way too hard for a script by modeling the behavior too closely on the internalgetopt_long()
implementation. And thus requiring the script to have a lot of boilerplate code just to handle error conditions that can be dealt with by the implementation.By making this a builtin we can do some useful things like create locally scoped variables in response to parsing the arguments. We can also avoid the need to specify a list of short flags and another list of long flags. The core idea is that every long flag has a corresponding short flag along with an indication of whether the option is a boolean or has an optional or mandatory argument. Also, whether the flag can appear more than once. Short flags don't have to have a corresponding long flag. The short flag associated with a long flag can be exposed or hidden as a valid flag.
Each option specification is composed of
/
if it can be used as a short flag else-
if it is only a short version of the long flag but it can't be used as a short flag (this is optional if there is no long flag name),Each short flag letter will result in a var name of the form
_flag_X
, whereX
is the short flag letter. It will be set with local scope (i.e., as if the script had doneset -l _flag_X
) if the corresponding flag (whether short or long) is seen in the arguments. If the flag is not a boolean the flag var will have zero or more values corresponding to the values collected when the args are parsed. If the flag is a boolean the value is a count of how many times the flag was seen. If the flag was not seen the flag var will not be set.In the following examples if a flag is not seen when parsing the arguments then the corresponding
_flag_X
var will not be set.Some examples:
h/help
means that both-h
and--help
are valid. The flag is a boolean and can be used more than once. If either flag is used then_flag_h
will be set to the count of how many times the flags were seen.h-help
means that only--help
is valid. The flag is a boolean and can be used more than once. If the long flag is used then_flag_h
will be set to the count of how many times the long flag was seen.n/name:
means that both-n
and--name
are valid. It requires a value and can be used at most once. If the flag is seen then_flag_n
will be set with the single mandatory value associated with the flag.n/name::
means that both-n
and--name
are valid. It accepts an optional value and can be used at most once. If the flag is seen then_flag_n
will be set with the value associated with the flag if one was provided else it will be set with no values.n-name+
means that only--name
is valid. It requires a value and can be used more than once. If the flag is seen then_flag_n
will be set with the values associated with each occurrence of the flag.x
means that only-x
is valid. It is a boolean can can be used more than once. If it is seen then_flag_x
will be set to the count of how many times the flag was seen.x:
,x::
, andx+
are similar to then/name
examples above but there is no long flag alternative to the short flag-x
.x-
is not valid since there is no long flag name and therefore the short flag,-x
, has to be usable. This is obviously true whether or not the specification also includes one of:
,::
,+
.After parsing the arguments the
argv
var is set (with local scope) to any values not already consumed during flag processing.If an error occurs during
fish_getopt
processing it will exit with a non-zero status. The specific value will indicate the nature of the problem. The specific exit status codes are TBD. The implementation will write error messages to stderr consistent with the builtin commands. For example, if a flag that requires an argument is seen without an argument then the equivalent of callingbuiltin_missing_argument()
will occur and$status
will be set to a distinct, non-zero, value.Flag specifications can be specified as a single value with each flag separated by a comma. Alternatively, you can specify each flag individually via
-o
or--option
. For example,or
In the above examples
_flag_h
will be set to the number of times-h
or--help
is seen,_flag_1
will be set to the number of times--search
is seen, and_flag_n
will be set if-n
or--name
is seen and is guaranteed to have a single value.The text was updated successfully, but these errors were encountered: