-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
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 use wgetopt_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/zsh getopts command. It makes option parsing way too hard for a script by modeling the behavior too closely on the internal getopt_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
- a short flag letter (which is mandatory in the specification),
- a
/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), - an optional long flag name,
- nothing if the flag is a boolean that takes no argument, else
- a plus-sign if it requires one value and the flag can be used more than once, else
- a single colon if it requires one value and the flag can be used at most once, else
- two colons if it takes an optional value and the flag can be used at most once.
Each short flag letter will result in a var name of the form _flag_X, where X is the short flag letter. It will be set with local scope (i.e., as if the script had done set -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/helpmeans that both-hand--helpare valid. The flag is a boolean and can be used more than once. If either flag is used then_flag_hwill be set to the count of how many times the flags were seen. -
h-helpmeans that only--helpis valid. The flag is a boolean and can be used more than once. If the long flag is used then_flag_hwill be set to the count of how many times the long flag was seen. -
n/name:means that both-nand--nameare val 82CA id. It requires a value and can be used at most once. If the flag is seen then_flag_nwill be set with the single mandatory value associated with the flag. -
n/name::means that both-nand--nameare valid. It accepts an optional value and can be used at most once. If the flag is seen then_flag_nwill 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--nameis valid. It requires a value and can be used more than once. If the flag is seen then_flag_nwill be set with the values associated with each occurrence of the flag. -
xmeans that only-xis valid. It is a boolean can can be used more than once. If it is seen then_flag_xwill be set to the count of how many times the flag was seen. -
x:,x::, andx+are similar to then/nameexamples 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 calling builtin_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,
fish_getopt 'h/help,1-search,n/name:' $argv
or
fish_getopt -o 'h/help' -o '1-search' -o 'n/name:' $argv
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.