Merge pull request #11698

This commit is contained in:
Johannes Altmanninger
2025-08-29 21:12:34 +02:00
72 changed files with 1485 additions and 319 deletions

View File

@@ -42,6 +42,16 @@ Deprecations and removed features
Scripting improvements
----------------------
- The ``psub`` command now allows combining ``--suffix`` with ``--fifo`` (:issue:`11729`).
- ``argparse`` now saves recognised options and values in ``$argv_opts``, allowing them to be forwarded to other commands (:issue:`6466`).
- ``argparse`` options can now be marked to be deleted from ``$argv_opts`` (by adding a ``&`` at the end of the option spec, before a ``!`` if present). There is now also a corresponding ``-d`` / ``--delete`` option to ``fish_opt``.
- ``argparse --ignore-unknown`` now removes preceding known short options from groups containing unknown options (e.g. when parsing ``-abc``, if ``a`` is known but ``b`` is not, then ``$argv`` will contain ``-bc``).
- ``argparse`` now has an ``-u`` / ``--move-unknown`` option that works like ``--ignore-unknown``, but unknown options (and their arguments) are moved from ``$argv`` to ``$argv_opts``. whereas ``--ignore-unknown`` keeps them in ``$argv``.
- ``argparse`` now has an ``-S`` / ``--strict-longopts`` option that forbids abbreviating long options or passing them with a single dash (e.g. if there is a long option called ``foo``, ``--fo`` and ``--foo`` won't match it).
- ``argparse`` now has a ``-U`` / ``--unknown-arguments`` *KIND* option, where *KIND* is either ``optional``, ``required``, or ``none``, indicating whether unknown options are parsed as taking optional, required, or no arguments. This implies ``--move-unknown``.
- ``argparse`` now allows specifying options that take multiple optional values by using ``=*`` in the option spec, the parsing of the option is the same as ones with optional values (i.e. ``=?``), but each successive use accumulates more values (or an empty string if no value), instead of replacing the previous value (i.e. it behaves similarly to ``=+``) (:issue:`8432`). In addition, ``fish_opt`` has been modified to support such options by using the ``--multiple-vals`` together with ``-o`` / ``--optional-val``; ``-m`` is also now acceptable as an abbreviation for ``--multiple-vals``.
- ``fish_opt`` no longer requires you give a short flag name when defining options, provided you give it a long flag name with more than one character.
- ``argparse`` option specifiers for long only options can now start with ``/``, allowing the definition of long options with a single letter (withouht the ``/``, an option with a single letter is always interpreted as a short flag). Due to this change, the ``--long-only`` option to ``fish_opt`` is now no longer necessary and is deprecated.
- ``fish_opt`` now has a ``-v`` / ``--validate`` option you can use to give a fish script to validate values of the option.
Interactive improvements
------------------------

View File

@@ -14,7 +14,7 @@ Synopsis
Description
-----------
This command makes it easy for fish scripts and functions to handle arguments. You pass arguments that define the known options, followed by a literal **--**, then the arguments to be parsed (which might also include a literal **--**). ``argparse`` then sets variables to indicate the passed options with their values, and sets ``$argv`` to the remaining arguments. See the :ref:`usage <cmd-argparse-usage>` section below.
This command makes it easy for fish scripts and functions to handle arguments. You pass arguments that define the known options, followed by a literal **--**, then the arguments to be parsed (which might also include a literal **--**). ``argparse`` then sets variables to indicate the passed options with their values, sets ``$argv_opts`` to the options and their values, and sets ``$argv`` to the remaining arguments. See the :ref:`usage <cmd-argparse-usage>` section below.
Each option specification (``OPTION_SPEC``) is written in the :ref:`domain specific language <cmd-argparse-option-specification>` described below. All OPTION_SPECs must appear after any argparse flags and before the ``--`` that separates them from the arguments to be parsed.
@@ -27,8 +27,8 @@ Options
The following ``argparse`` options are available. They must appear before all *OPTION_SPEC*\ s:
**-n** or **--name**
The command name for use in error messages. By default the current function name will be used, or ``argparse`` if run outside of a function.
**-n** or **--name** *NAME*
Use *NAME* in error messages. By default the current function name will be used, or ``argparse`` if run outside of a function.
**-x** or **--exclusive** *OPTIONS*
A comma separated list of options that are mutually exclusive. You can use this more than once to define multiple sets of mutually exclusive options.
@@ -40,8 +40,44 @@ The following ``argparse`` options are available. They must appear before all *O
**-X** or **--max-args** *NUMBER*
The maximum number of acceptable non-option arguments. The default is infinity.
**-u** or **--move-unknown**
Allow unknown options, and move them from ``$argv`` to ``$argv_opts``. By default, Unknown options are treated as if they take optional arguments (i.e. have option spec ``=?``).
The above means that if a group of short options contains an unknown short option *followed* by a known short option, the known short option is
treated as an argument to the unknown one (e.g. ``--move-unknown h -- -oh`` will treat ``h`` as the argument to ``-o``, and so ``_flag_h`` will *not* be set).
In contrast, if the known option comes first (and does not take any arguments), the known option will be recognised (e.g. ``argparse --move-unknown h -- -ho`` *will* set ``$_flag_h`` to ``-h``)
**-i** or **--ignore-unknown**
Ignores unknown options, keeping them and their arguments in $argv instead.
Deprecated. This is like **--move-unknown**, except that unknown options and their arguments are kept in ``$argv`` and not moved to ``$argv_opts``. Unlike **--move-unknown**, this option makes it impossible to distinguish between an unknown option and non-option argument that starts with a ``-`` (since any ``--`` seperator in ``$argv`` will be removed).
**-S** or **--strict-longopts**
This makes the parsing of long options more strict. In particular, *without* this flag, if ``long`` is a known long option flag, ``--long`` and ``--long=<value>`` can be abbreviated as:
- ``-long`` and ``-long=<value>``, but *only* if there is no short flag ``l``.
- ``--lo`` and ``--lo=<value>``, but *only* if there is no other long flag that starts with ``lo``. Similarly with any other non-empty prefix of ``long``.
- ``-lo`` and ``-lo=<value>`` (i.e. combining the above two).
With the ``--strict-longopts`` flag, the above three are parse errors: one must use the syntax ``--long`` or ``--long=<value>`` to use a long option called ``long``.
This flag has no effect on the parsing of unknown options (which are parsed as if this flag is on).
This option may be on all the time in the future, so do not rely on the behaviour without it.
**--unknown-arguments** *KIND*
This option implies **--move-unknown**, unless **--ignore-unknown** is also given.
This will modify the parsing behaviour of unknown options depending on the value of *KIND*:
- **optional** (the default), allows each unknown option to take an optional argument (i.e. as if it had ``=?`` or ``=*`` in its option specification). For example, ``argparse --ignore-unknown --unknown-arguments=optional ab -- -u -a -ub`` will set ``_flag_a`` but *not* ``_flag_b``, as the ``b`` is treated as an argument to the second use of ``-u``.
- **required** requires each unknown option to take an argument (i.e. as if it had ``=`` or ``=+`` in its option specification). If the above example was changed to use ``--unknown-arguments=required``, *neither* ``_flag_a`` nor ``_flag_b`` would be set: the ``-a`` will be treated as an argument to the first use of ``-u``, and the ``b`` as an argument to the second.
- **none** forbids each unknown option from taking an argument (i.e. as if it had no ``=`` in its option specification). If the above example was changed to use ``--unknown-arguments=none``, *both* ``_flag_a`` and ``_flag_b`` would be set, as neither use of ``-u`` will be passed as taking an argument.
Note that the above assumes that unknown long flags use the ``--`` "GNU-style" (e.g. if *KIND* is ``none``, and there is no ``bar`` long option, ``-bar`` is interpreted as three short flags, ``b``, ``a``, and ``r``; but if ``bar`` is known, ``-bar`` is treated the same as ``--bar``).
When using ``--unknown-arguments=required``, you will get an error if the provided arguments end in an unknown option, since it has no argument. Similarly, with ``--unknown-arguments=none``, you will get an error if you use the ``--flag=value`` syntax and ``flag`` is an unknown option.
**-s** or **--stop-nonopt**
Causes scanning the arguments to stop as soon as the first non-option argument is seen. Among other things, this is useful to implement subcommands that have their own options.
@@ -91,7 +127,7 @@ But this is not::
set -l argv
argparse 'h/help' 'n/name' $argv
The first ``--`` seen is what allows the ``argparse`` command to reliably separate the option specifications and options to ``argparse`` itself (like ``--ignore-unknown``) from the command arguments, so it is required.
The first ``--`` seen is what allows the ``argparse`` command to reliably separate the option specifications and options to ``argparse`` itself (like ``--move-unknown``) from the command arguments, so it is required.
.. _cmd-argparse-option-specification:
@@ -100,9 +136,13 @@ Option Specifications
Each option specification consists of:
- An optional alphanumeric short flag character, followed by a ``/`` if the short flag can be used by someone invoking your command or, for backwards compatibility, a ``-`` if it should not be exposed as a valid short flag (in which case it will also not be exposed as a flag variable).
- An optional alphanumeric short flag character.
- An optional long flag name, which if not present the short flag can be used, and if that is also not present, an error is reported
- An optional long flag name preceded by a ``/``. If neither a short flag nor long flag are present, an error is reported.
- If there is no short flag, and the long flag name is more than one character, the ``/`` can be omitted.
- For backwards compatibility, if there is a short and a long flag, a ``-`` can be used in place of the ``/``, if the short flag is not to be usable by users (in which case it will also not be exposed as a flag variable).
- Nothing if the flag is a boolean that takes no argument or is an integer flag, or
@@ -110,9 +150,15 @@ Each option specification consists of:
- **=?** if it takes an optional value and only the last instance of the flag is saved, or
- **=+** if it requires a value and each instance of the flag is saved.
- **=+** if it requires a value and each instance of the flag is saved, or
- Optionally a ``!`` followed by fish script to validate the value. Typically this will be a function to run. If the exit status is zero the value for the flag is valid. If non-zero the value is invalid. Any error messages should be written to stdout (not stderr). See the section on :ref:`Flag Value Validation <flag-value-validation>` for more information.
- **=\*** if it takes an optional value *and* each instance of the flag is saved, storing the empty string when the flag was not given a value.
- Optionally a ``&``, indicating that the option and any attached values are not to be saved in ``$argv`` or ``$argv_opts``. This does not affect the the ``_flag_`` variables.
- Nothing if the flag is a boolean that takes no argument, or
- ``!`` followed by fish script to validate the value. Typically this will be a function to run. If the exit status is zero the value for the flag is valid. If non-zero the value is invalid. Any error messages should be written to stdout (not stderr). See the section on :ref:`Flag Value Validation <flag-value-validation>` for more information.
See the :doc:`fish_opt <fish_opt>` command for a friendlier but more verbose way to create option specifications.
@@ -132,7 +178,7 @@ This does not read numbers given as ``+NNN``, only those that look like flags -
Note: Optional arguments
------------------------
An option defined with ``=?`` can take optional arguments. Optional arguments have to be *directly attached* to the option they belong to.
An option defined with ``=?`` or ``=*`` can take optional arguments. Optional arguments have to be *directly attached* to the option they belong to.
That means the argument will only be used for the option if you use it like::
@@ -199,16 +245,22 @@ Some *OPTION_SPEC* examples:
- ``help`` means that only ``--help`` is valid. The flag is a boolean and can be used more than once. If it is used then ``_flag_help`` will be set as above. Also ``h-help`` (with an arbitrary short letter) for backwards compatibility.
- ``help&`` is similar (it will *remove* ``--help`` from ``$argv``), the difference is that ``--help``` will *not* placed in ``$argv_opts``.
- ``longonly=`` is a flag ``--longonly`` that requires an option, there is no short flag or even short flag variable.
- ``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`` and ``_flag_name`` 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`` and ``_flag_name`` will be set with the value associated with the flag if one was provided else it will be set with no values.
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurence. Each value will be the value given to the option, or the empty string if no value was given.
- ``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_name`` will be set with the values associated with each occurrence.
- ``x`` means that only ``-x`` is valid. It is a boolean that can be used more than once. If it is seen then ``_flag_x`` will be set as above.
- ``/x`` is similar, but only ``--x`` is valid (instead of ``-x``).
- ``x=``, ``x=?``, and ``x=+`` are similar to the n/name examples above but there is no long flag alternative to the short flag ``-x``.
- ``#max`` (or ``#-max``) means that flags matching the regex "^--?\\d+$" are valid. When seen they are assigned to the variable ``_flag_max``. This allows any valid positive or negative integer to be specified by prefixing it with a single "-". Many commands support this idiom. For example ``head -3 /a/file`` to emit only the first three lines of /a/file.
@@ -217,7 +269,7 @@ Some *OPTION_SPEC* examples:
- ``#longonly`` causes the last integer option to be stored in ``_flag_longonly``.
After parsing the arguments the ``argv`` variable is set with local scope to any values not already consumed during flag processing. If there are no unbound values the variable is set but ``count $argv`` will be zero.
After parsing the arguments the ``argv`` variable is set with local scope to any values not already consumed during flag processing. If there are no unbound values the variable is set but ``count $argv`` will be zero. Similarly, the ``argv_opts`` variable is set with local scope to the arguments that *were* consumed during flag processing. This allows forwarding ``$argv_opts`` to another command, together with additional arguments.
If an error occurs during argparse processing it will exit with a non-zero status and print error messages to stderr.
@@ -259,17 +311,41 @@ After this it figures out which variable it should operate on according to the `
and set $var $result
Limitations
-----------
An example of using ``$argv_opts`` to forward known options to another command, whilst adding new options::
One limitation with **--ignore-unknown** is that, if an unknown option is given in a group with known options, the entire group will be kept in $argv. ``argparse`` will not do any permutations here.
function my-head
# The following option is the only existing one to head that takes arguments
# (we will forward it verbatim).
set -l opt_spec n/lines=
# --qwords is a new option, but --bytes is an existing one which we will modify below
set -a opt_spec "qwords=&" "c/bytes=&"
argparse --strict-longopts --move-unknown --unknown-arguments=none $opt_spec -- $argv || return
if set -q _flag_qwords
# --qwords allows specifying the size in multiples of 8 bytes
set -a argv_opts --bytes=(math -- $_flag_qwords \* 8 || return)
else if set -q _flag_bytes
# Allows using a 'q' suffix, e.g. --bytes=4q to mean 4*8 bytes.
if string match -qr 'q$' -- $_flag_bytes
set -a argv_opts --bytes=(math -- (string replace -r 'q$' '*8' -- $_flag_bytes) || return)
else
# Keep the users setting
set -a argv_opts --bytes=$_flag_bytes
end
For instance::
end
argparse --ignore-unknown h -- -ho
echo $_flag_h # is -h, because -h was given
echo $argv # is still -ho
if test (count $argv) -eq 0
# Default to heading /dev/kmsg (whereas head defaults to stdin)
set -l argv /dev/kmsg
end
This limitation may be lifted in future.
# Call the real head with our modified options and arguments.
head $argv_opts -- $argv
end
Additionally, it can only parse known options up to the first unknown option in the group - the unknown option could take options, so it isn't clear what any character after an unknown option means.
The argparse call above saves all the options we do *not* want to process in ``$argv_opts``. (The ``--qwords`` and ``--bytes`` options are *not* saved there as their option spec's end in a ``~``). The code then processes the ``--qwords`` and ``--bytess`` options using the the ``$_flag_OPTION`` variables, and puts the transformed options in ``$argv_opts`` (which already contains all the original options, *other* than ``--qwords`` and ``--bytes``).
Note that because the ``argparse`` call above uses ``--move-unknown`` and ``--unknown-arguments=none``, we only need to tell it the arguments to ``head`` that take a value. This allows the wrapper script to accurately work out the *non*-option arguments (i.e. ``$argv``, the filenames that ``head`` is to operate on). Using ``--unknown-arguments=optional`` and explicitly listing all the known options to ``head`` however would have the advantage that if ``head`` were to add new options, they could still be used with the wrapper script using the "stuck" form for arguments (e.g. ``-o<arg>``, or ``--opt=<arg>``).
Note that the ``--strict-longopts`` is required to be able to correctly pass short options, e.g. without it ``my-head -q --bytes 10q``, will actually parse the ``-q`` as shorthand for ``--qwords``.

View File

@@ -8,7 +8,7 @@ Synopsis
.. synopsis::
fish_opt [(-slor | --multiple-vals=) OPTNAME]
fish_opt [-s ALPHANUM] [-l LONG-NAME] [-ormd] [--long-only] [-v COMMAND OPTIONS ... ]
fish_opt --help
Description
@@ -18,14 +18,14 @@ This command provides a way to produce option specifications suitable for use wi
The following ``argparse`` options are available:
**-s** or **--short**
Takes a single letter that is used as the short flag in the option being defined. This option is mandatory.
**-s** or **--short** *ALPHANUM*
Takes a single letter or number that is used as the short flag in the option being defined. Either this option or the **--long** option must be provided.
**-l** or **--long**
**-l** or **--long** *LONG-NAME*
Takes a string that is used as the long flag in the option being defined. This option is optional and has no default. If no long flag is defined then only the short flag will be allowed when parsing arguments using the option specification.
**--long-only**
The option being defined will only allow the long flag name to be used. The short flag name must still be defined (i.e., **--short** must be specified) but it cannot be used when parsing arguments using this option specification.
Deprecated. The option being defined will only allow the long flag name to be used, even if the short flag is defined (i.e., **--short** is specified).
**-o** or **--optional-val**
The option being defined can take a value, but it is optional rather than required. If the option is seen more than once when parsing arguments, only the last value seen is saved. This means the resulting flag variable created by ``argparse`` will zero elements if no value was given with the option else it will have exactly one element.
@@ -33,8 +33,15 @@ The following ``argparse`` options are available:
**-r** or **--required-val**
The option being defined requires a value. If the option is seen more than once when parsing arguments, only the last value seen is saved. This means the resulting flag variable created by ``argparse`` will have exactly one element.
**--multiple-vals**
The option being defined requires a value each time it is seen. Each instance is stored. This means the resulting flag variable created by ``argparse`` will have one element for each instance of this option in the arguments.
**-m** or **--multiple-vals**
The value of each instance of the option is accumulated. If **--optional-val** is provided, the value is optional, and an empty string is stored if no value is provided. Otherwise, the **--requiured-val** option is implied and each instance of the option requires a value. This means the resulting flag variable created by ``argparse`` will have one element for each instance of this option in the arguments, even for instances that did not provide a value.
**-d** or **--delete**
The option and any values will be deleted from the ``$argv_opts`` variables set by ``argparse``
(as with other options, it will also be deleted from ``$argv``).
**-v** or **--validate** *COMMAND* *OPTION...*
This option must be the last one, and requires one of ``-o``, ``-r``, or ``-m``. All the remaining arguments are interpreted a fish script to run to validate the value of the argument, see ``argparse`` documentation for more details. Note that the interpretation of *COMMAND* *OPTION...* is similar to ``eval``, so you may need to quote or escape special characters *twice* if you want them to be interpreted literally when the validate script is run.
**-h** or **--help**
Displays help about using this command.
@@ -59,18 +66,25 @@ Same as above but with a second flag that requires a value:
::
set -l options (fish_opt -s h -l help)
set options $options (fish_opt -s m -l max --required-val)
set options $options (fish_opt -s m -l max -r)
argparse $options -- $argv
Same as above but the value of the second flag cannot be the empty string:
Same as above but with a third flag that can be given multiple times saving the value of each instance seen and only the long flag name (``--token``) can be used:
::
set -l options (fish_opt -s h -l help)
set options $options (fish_opt -s m -l max -rv test \$_flag_valu != "''")
argparse $options -- $argv
Same as above but with a third flag that can be given multiple times saving the value of each instance seen and only a long flag name (``--token``) is defined:
::
set -l options (fish_opt --short=h --long=help)
set options $options (fish_opt --short=m --long=max --required-val)
set options $options (fish_opt --short=t --long=token --multiple-vals --long-only)
set options $options (fish_opt --short=m --long=max --required-val --validate test \$_flag_valu != "''")
set options $options (fish_opt --long=token --multiple-vals)
argparse $options -- $argv

View File

@@ -196,6 +196,11 @@ msgstr "%ls: %ls: ungültiger Unterbefehl\n"
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
#, c-format
msgid "%ls: %ls: option requires an argument\n"
msgstr ""
@@ -408,6 +413,11 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
#, c-format
msgid "%ls: Invalid count value '%ls'\n"
msgstr "%ls: Ungültiger 'count'-Wert '%ls'\n"
@@ -1920,19 +1930,34 @@ msgstr ""
msgid "%s: Directory stack is empty…\\n"
msgstr ""
msgid "%s: Either the --short or --long flag must be provided\\n"
msgstr ""
msgid "%s: Expected 1, 2 or 3 arguments, got %d\\n"
msgstr ""
msgid "%s: Expected exactly one argument, got %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgstr ""
msgid "%s: Extra non-option arguments were provided\\n"
msgstr ""
msgid "%s: Invalid mask '%s'\\n"
msgstr "%s: Ungültige Maske '%s'\\n"
msgid "%s: Not inside of command substitution"
msgstr ""
msgid "%s: The --short flag is required and must be a single character\\n"
msgid "%s: The --long-only flag requires the --long flag\\n"
msgstr ""
msgid "%s: The --short flag must be a single character\\n"
msgstr ""
msgid "%s: The --validate flag requires subsequent arguments\\n"
msgstr ""
msgid "%s: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag\\n"
msgstr ""
msgid "%s: The number of positions to skip must be a non-negative integer\\n"
@@ -13617,6 +13642,9 @@ msgstr ""
msgid "Delete only those tarballs which aren't present in a snapshot"
msgstr ""
msgid "Delete option from argv_opts"
msgstr ""
msgid "Delete previous settime entry"
msgstr ""
@@ -25425,6 +25453,9 @@ msgstr ""
msgid "First remove existing destination files"
msgstr ""
msgid "Fish script to validate option values"
msgstr ""
msgid "Fish's release notes"
msgstr ""

View File

@@ -194,6 +194,11 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
#, c-format
msgid "%ls: %ls: option requires an argument\n"
msgstr ""
@@ -406,6 +411,11 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
#, c-format
msgid "%ls: Invalid count value '%ls'\n"
msgstr ""
@@ -1916,19 +1926,34 @@ msgstr "%s: Could not find a web browser.\\n"
msgid "%s: Directory stack is empty…\\n"
msgstr ""
msgid "%s: Either the --short or --long flag must be provided\\n"
msgstr ""
msgid "%s: Expected 1, 2 or 3 arguments, got %d\\n"
msgstr "%s: Expected 1, 2 or 3 arguments, got %d\\n"
msgid "%s: Expected exactly one argument, got %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgstr "%s: Expected exactly one argument, got %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgid "%s: Extra non-option arguments were provided\\n"
msgstr ""
msgid "%s: Invalid mask '%s'\\n"
msgstr "%s: Invalid mask “%s”\\n"
msgid "%s: Not inside of command substitution"
msgstr "%s: Not inside of command substitution"
msgid "%s: The --short flag is required and must be a single character\\n"
msgid "%s: The --long-only flag requires the --long flag\\n"
msgstr ""
msgid "%s: The --short flag must be a single character\\n"
msgstr ""
msgid "%s: The --validate flag requires subsequent arguments\\n"
msgstr ""
msgid "%s: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag\\n"
msgstr ""
msgid "%s: The number of positions to skip must be a non-negative integer\\n"
@@ -13613,6 +13638,9 @@ msgstr ""
msgid "Delete only those tarballs which aren't present in a snapshot"
msgstr ""
msgid "Delete option from argv_opts"
msgstr ""
msgid "Delete previous settime entry"
msgstr ""
@@ -25421,6 +25449,9 @@ msgstr ""
msgid "First remove existing destination files"
msgstr ""
msgid "Fish script to validate option values"
msgstr ""
msgid "Fish's release notes"
msgstr ""

View File

@@ -295,6 +295,11 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
#, c-format
msgid "%ls: %ls: option requires an argument\n"
msgstr ""
@@ -507,6 +512,11 @@ msgstr "%ls : Valeur --max-args '%ls' invalide\n"
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr "%ls : Valeur --min-args '%ls' invalide\n"
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
#, c-format
msgid "%ls: Invalid count value '%ls'\n"
msgstr "%ls : compte '%ls' invalide\n"
@@ -2017,20 +2027,35 @@ msgstr "%s : Impossible de trouver un navigateur Web.\\n"
msgid "%s: Directory stack is empty…\\n"
msgstr "%s : Pile de dossiers vide…\\n"
msgid "%s: Either the --short or --long flag must be provided\\n"
msgstr ""
msgid "%s: Expected 1, 2 or 3 arguments, got %d\\n"
msgstr "%s : 1, 2 ou 3 arguments attendus, %d reçu(s)\\n"
msgid "%s: Expected exactly one argument, got %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgstr "%s : Un argument attendu, %s reçu(s).\\n\\nRésumé :\\n\\t%svared%s VARIABLE\\n"
msgid "%s: Extra non-option arguments were provided\\n"
msgstr ""
msgid "%s: Invalid mask '%s'\\n"
msgstr "%s : Masque '%s' invalide\\n"
msgid "%s: Not inside of command substitution"
msgstr "%s : hors de la substitution de commande"
msgid "%s: The --short flag is required and must be a single character\\n"
msgstr "%s : Loption --short est requise et doit être un caractère unique\\n"
msgid "%s: The --long-only flag requires the --long flag\\n"
msgstr ""
msgid "%s: The --short flag must be a single character\\n"
msgstr ""
msgid "%s: The --validate flag requires subsequent arguments\\n"
msgstr ""
msgid "%s: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag\\n"
msgstr ""
msgid "%s: The number of positions to skip must be a non-negative integer\\n"
msgstr "%s : Le nombre de positions à passer doit être un entier naturel\\n"
@@ -13714,6 +13739,9 @@ msgstr ""
msgid "Delete only those tarballs which aren't present in a snapshot"
msgstr ""
msgid "Delete option from argv_opts"
msgstr ""
msgid "Delete previous settime entry"
msgstr ""
@@ -25522,6 +25550,9 @@ msgstr ""
msgid "First remove existing destination files"
msgstr ""
msgid "Fish script to validate option values"
msgstr ""
msgid "Fish's release notes"
msgstr ""

View File

@@ -190,6 +190,11 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
#, c-format
msgid "%ls: %ls: option requires an argument\n"
msgstr ""
@@ -402,6 +407,11 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
#, c-format
msgid "%ls: Invalid count value '%ls'\n"
msgstr ""
@@ -1912,19 +1922,34 @@ msgstr ""
msgid "%s: Directory stack is empty…\\n"
msgstr ""
msgid "%s: Either the --short or --long flag must be provided\\n"
msgstr ""
msgid "%s: Expected 1, 2 or 3 arguments, got %d\\n"
msgstr ""
msgid "%s: Expected exactly one argument, got %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgstr ""
msgid "%s: Extra non-option arguments were provided\\n"
msgstr ""
msgid "%s: Invalid mask '%s'\\n"
msgstr ""
msgid "%s: Not inside of command substitution"
msgstr ""
msgid "%s: The --short flag is required and must be a single character\\n"
msgid "%s: The --long-only flag requires the --long flag\\n"
msgstr ""
msgid "%s: The --short flag must be a single character\\n"
msgstr ""
msgid "%s: The --validate flag requires subsequent arguments\\n"
msgstr ""
msgid "%s: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag\\n"
msgstr ""
msgid "%s: The number of positions to skip must be a non-negative integer\\n"
@@ -13609,6 +13634,9 @@ msgstr ""
msgid "Delete only those tarballs which aren't present in a snapshot"
msgstr ""
msgid "Delete option from argv_opts"
msgstr ""
msgid "Delete previous settime entry"
msgstr ""
@@ -25417,6 +25445,9 @@ msgstr ""
msgid "First remove existing destination files"
msgstr ""
msgid "Fish script to validate option values"
msgstr ""
msgid "Fish's release notes"
msgstr ""

View File

@@ -195,6 +195,11 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
#, c-format
msgid "%ls: %ls: option requires an argument\n"
msgstr ""
@@ -407,6 +412,11 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
#, c-format
msgid "%ls: Invalid count value '%ls'\n"
msgstr ""
@@ -1917,19 +1927,34 @@ msgstr ""
msgid "%s: Directory stack is empty…\\n"
msgstr ""
msgid "%s: Either the --short or --long flag must be provided\\n"
msgstr ""
msgid "%s: Expected 1, 2 or 3 arguments, got %d\\n"
msgstr ""
msgid "%s: Expected exactly one argument, got %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgstr ""
msgid "%s: Extra non-option arguments were provided\\n"
msgstr ""
msgid "%s: Invalid mask '%s'\\n"
msgstr ""
msgid "%s: Not inside of command substitution"
msgstr ""
msgid "%s: The --short flag is required and must be a single character\\n"
msgid "%s: The --long-only flag requires the --long flag\\n"
msgstr ""
msgid "%s: The --short flag must be a single character\\n"
msgstr ""
msgid "%s: The --validate flag requires subsequent arguments\\n"
msgstr ""
msgid "%s: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag\\n"
msgstr ""
msgid "%s: The number of positions to skip must be a non-negative integer\\n"
@@ -13614,6 +13639,9 @@ msgstr ""
msgid "Delete only those tarballs which aren't present in a snapshot"
msgstr ""
msgid "Delete option from argv_opts"
msgstr ""
msgid "Delete previous settime entry"
msgstr ""
@@ -25432,6 +25460,9 @@ msgstr ""
msgid "First remove existing destination files"
msgstr ""
msgid "Fish script to validate option values"
msgstr ""
msgid "Fish's release notes"
msgstr ""

View File

@@ -191,6 +191,11 @@ msgstr ""
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr ""
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
#, c-format
msgid "%ls: %ls: option requires an argument\n"
msgstr ""
@@ -403,6 +408,11 @@ msgstr ""
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr ""
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
#, c-format
msgid "%ls: Invalid count value '%ls'\n"
msgstr ""
@@ -1913,19 +1923,34 @@ msgstr "%s: Kunde inte hitta en webbrowser\\n"
msgid "%s: Directory stack is empty…\\n"
msgstr ""
msgid "%s: Either the --short or --long flag must be provided\\n"
msgstr ""
msgid "%s: Expected 1, 2 or 3 arguments, got %d\\n"
msgstr "%s: Förväntade 1, 2 eller 3 argument, fick %d\\n"
msgid "%s: Expected exactly one argument, got %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgstr "%s: Förväntade exakt ett argument, fick %s\\n\\nSynopsis:\\n\\t%svared%s VARIABEL\\n"
msgid "%s: Extra non-option arguments were provided\\n"
msgstr ""
msgid "%s: Invalid mask '%s'\\n"
msgstr "%s: Ogiltigt mask '%s'\\n"
msgid "%s: Not inside of command substitution"
msgstr ""
msgid "%s: The --short flag is required and must be a single character\\n"
msgid "%s: The --long-only flag requires the --long flag\\n"
msgstr ""
msgid "%s: The --short flag must be a single character\\n"
msgstr ""
msgid "%s: The --validate flag requires subsequent arguments\\n"
msgstr ""
msgid "%s: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag\\n"
msgstr ""
msgid "%s: The number of positions to skip must be a non-negative integer\\n"
@@ -13612,6 +13637,9 @@ msgstr ""
msgid "Delete only those tarballs which aren't present in a snapshot"
msgstr ""
msgid "Delete option from argv_opts"
msgstr ""
msgid "Delete previous settime entry"
msgstr ""
@@ -25420,6 +25448,9 @@ msgstr ""
msgid "First remove existing destination files"
msgstr ""
msgid "Fish script to validate option values"
msgstr ""
msgid "Fish's release notes"
msgstr ""

View File

@@ -188,6 +188,11 @@ msgstr "%ls: %ls:无效的子命令\n"
msgid "%ls: %ls: invalid variable name. See `help identifiers`\n"
msgstr "%ls: %ls:无效的可变名称. 参见`help identifiers`\n"
#
#, c-format
msgid "%ls: %ls: option does not take an argument\n"
msgstr ""
#, c-format
msgid "%ls: %ls: option requires an argument\n"
msgstr "%ls:%ls: 选项需要参数\n"
@@ -400,6 +405,11 @@ msgstr "%ls: 无效的 --max-args 值 '%ls'\n"
msgid "%ls: Invalid --min-args value '%ls'\n"
msgstr "%ls: 无效的 --min-args 值 '%ls'\n"
#
#, c-format
msgid "%ls: Invalid --unknown-arguments value '%ls'\n"
msgstr ""
#, fuzzy, c-format
msgid "%ls: Invalid count value '%ls'\n"
msgstr "%s: 无效的计数值 '%s'\n"
@@ -1915,20 +1925,35 @@ msgstr "%s: 找不到网页浏览器.\\n"
msgid "%s: Directory stack is empty…\\n"
msgstr "%s: 目录堆栈为空...\\n"
msgid "%s: Either the --short or --long flag must be provided\\n"
msgstr ""
msgid "%s: Expected 1, 2 or 3 arguments, got %d\\n"
msgstr "%s: 预期参数 1, 2 或 3, 获得 %d\\n"
msgid "%s: Expected exactly one argument, got %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgstr "%s: 需要精确的一条参数, 获得 %s.\\n\\nSynopsis:\\n\\t%svared%s VARIABLE\\n"
msgid "%s: Extra non-option arguments were provided\\n"
msgstr ""
msgid "%s: Invalid mask '%s'\\n"
msgstr "%s:无效的掩码'%s'\\n"
msgid "%s: Not inside of command substitution"
msgstr "%s: 命令替换不在内"
msgid "%s: The --short flag is required and must be a single character\\n"
msgstr "%s:需要 --short旗并必须是单一字符\\n"
msgid "%s: The --long-only flag requires the --long flag\\n"
msgstr ""
msgid "%s: The --short flag must be a single character\\n"
msgstr ""
msgid "%s: The --validate flag requires subsequent arguments\\n"
msgstr ""
msgid "%s: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag\\n"
msgstr ""
msgid "%s: The number of positions to skip must be a non-negative integer\\n"
msgstr "%s:要跳过的位置数必须是非负整数\\n"
@@ -13612,6 +13637,9 @@ msgstr "只删除 libvirt 元数据, 留下快照内容"
msgid "Delete only those tarballs which aren't present in a snapshot"
msgstr ""
msgid "Delete option from argv_opts"
msgstr ""
msgid "Delete previous settime entry"
msgstr "删除上一个设定时间项"
@@ -25420,6 +25448,9 @@ msgstr "要转换的第一个页面"
msgid "First remove existing destination files"
msgstr "首先删除已有目的文件"
msgid "Fish script to validate option values"
msgstr ""
msgid "Fish's release notes"
msgstr "fish的放行记录"

View File

@@ -67,6 +67,15 @@ complete --command argparse --short-option N --long-option min-args --no-files -
complete --command argparse --short-option X --long-option max-args --no-files --require-parameter \
--description 'Specify maximum non-option argument count'
complete --command argparse --short-option i --long-option ignore-unknown \
-n '! __fish_seen_argument --short u --long move-unknown' \
--description 'Ignore unknown options'
complete --command argparse --short-option u --long-option move-unknown \
-n '! __fish_seen_argument --short i --long ignore-unknown' \
--description 'Move unknown options into \$argv_opts'
complete --command argparse --short-option S --long-option strict-longopts \
--description 'Pass long options strictly'
complete --command argparse --short-option U --long-option unknown-arguments --no-files --require-parameter \
--arguments "optional required none" \
--description 'Whether unknown options can have arguments'
complete --command argparse --short-option s --long-option stop-nonopt \
--description 'Exit on subcommand'

View File

@@ -29,9 +29,9 @@ function __fish_conda_subcommand
# get the commandline args without the "conda"
set -l toks (commandline -xpc)[2..-1]
# Remove any important options - if we had options with arguments,
# if we had options with arguments,
# they'd need to be listed here to be removed.
argparse -i h/help v/version -- $toks 2>/dev/null
argparse -u -- $toks 2>/dev/null
# Return false if it fails - this shouldn't really happen,
# so all bets are off
or return 2
@@ -44,19 +44,12 @@ function __fish_conda_subcommand
if test "$subcmds[1]" = "$argv[1]"
set -e argv[1]
set -e subcmds[1]
else if string match -q -- '-*' $argv[1]
set -e argv[1]
else
return 1
end
end
# Skip any remaining options.
while string match -q -- '-*' $argv[1]
set -e argv[1]
end
# If we have no subcommand left,
# If we have no subcommand
# we either matched all given subcommands or we need one.
if not set -q argv[1]
return $have_sub

View File

@@ -9,4 +9,6 @@ complete --command fish_opt --short-option l --long-option long --no-files --req
complete --command fish_opt --long-option longonly --description 'Use only long option'
complete --command fish_opt --short-option o --long-option optional-val -n $CONDITION --description 'Don\'t require value'
complete --command fish_opt --short-option r --long-option required-val -n $CONDITION --description 'Require value'
complete --command fish_opt --long-option multiple-vals --description 'Store all values'
complete --command fish_opt --short-option m --long-option multiple-vals --description 'Store all values'
complete --command fish_opt --short-option d --long-option delete --description 'Delete option from argv_opts'
complete --command fish_opt --short-option v --long-option validate --require-parameter --description 'Fish script to validate option values'

View File

@@ -8,10 +8,10 @@ function __iwctl_filter -w iwctl
# awk does not work on multiline entries, therefore we use string match,
# which has the added benefit of filtering out the `No devices in ...` lines
argparse -i all-columns -- $argv
argparse -u all-columns -- $argv
# remove color escape sequences
set -l results (iwctl $argv | string replace -ra '\e\[[\d;]+m' '')
set -l results (iwctl $argv_opts -- $argv | string replace -ra '\e\[[\d;]+m' '')
# calculate column widths
set -l headers $results[3]
# We exploit the fact that all column labels will have >2 space to the left, and inside column labels there is always only one space.
@@ -38,7 +38,7 @@ function __iwctl_match_subcoms
set argv (commandline -pxc)
# iwctl allows to specify arguments for username, password, passphrase and dont-ask regardless of any following commands
argparse -i 'u/username=' 'p/password=' 'P/passphrase=' v/dont-ask -- $argv
argparse -u 'u/username=' 'p/password=' 'P/passphrase=' v/dont-ask -- $argv
set argv $argv[2..]
if test (count $argv) != (count $match)
@@ -55,7 +55,7 @@ end
function __iwctl_connect
set argv (commandline -pxc)
# remove all options
argparse -i 'u/username=' 'p/password=' 'P/passphrase=' v/dont-ask -- $argv
argparse -u 'u/username=' 'p/password=' 'P/passphrase=' v/dont-ask -- $argv
# station name should now be the third argument (`iwctl station <wlan>`)
for network in (__iwctl_filter station $argv[3] get-networks rssi-dbms --all-columns)
set network (string split \t -- $network)

View File

@@ -20,7 +20,7 @@ end
function __fish_meson_builddir
# Consider the value of -C option to detect the build directory
set -l cmd (commandline -xpc)
argparse -i 'C=' -- $cmd
argparse -u 'C=' -- $cmd
if set -q _flag_C
echo $_flag_C
else

View File

@@ -1,7 +1,7 @@
function __fish_ninja
set -l saved_args $argv
set -l dir .
if argparse -i C/dir= -- (commandline -xpc)
if argparse -u C/dir= -- (commandline -xpc)
command ninja -C$_flag_C $saved_args
end
end

View File

@@ -61,11 +61,13 @@ function __fish_complete_yadm_like_git
set -l yadm_work_tree (yadm gitconfig --get core.worktree)
set -l yadm_repo (yadm introspect repo)
argparse -i 'R-yadm-repo=' -- $cmdline 2>/dev/null
argparse -u 'R-yadm-repo=&' -- $cmdline 2>/dev/null
if set -q _flag_yadm_repo
set yadm_repo $_flag_yadm_repo
# argparse *always* sets $argv to remaining arguments after consuming specified options
set cmdline $argv
# argparse -u *always* sets $argv to remaining arguments after consuming all options,
# and it *always* sets $argv_opts to any specified non-& options and any unknown options
# (the -- is needed in case $argv originally contained a -- followed by arguments starting with a -)
set cmdline $argv_opts -- $argv
end
set -l git_wrapper_cmd "git --work-tree $yadm_work_tree --git-dir $yadm_repo $cmdline"

View File

@@ -1,5 +1,5 @@
function __fish_mysql_query -a query
argparse -i 'u/user=' 'P/port=' 'h/host=' 'p/password=?' 'S/socket=' -- (commandline -px)
argparse -u 'u/user=' 'P/port=' 'h/host=' 'p/password=?' 'S/socket=' -- (commandline -px)
set -l mysql_cmd mysql
for flag in u P h S
if set -q _flag_$flag

View File

@@ -1,5 +1,5 @@
function __fish_seen_argument --description 'Check whether argument is used'
argparse --ignore-unknown 's/short=+' 'o/old=+' 'l/long=+' 'w/windows=+' -- $argv
argparse --move-unknown 's/short=+&' 'o/old=+&' 'l/long=+&' 'w/windows=+&' -- $argv
set --local tokens (commandline --current-process --tokens-expanded --cut-at-cursor)
set --erase tokens[1]
@@ -30,7 +30,7 @@ function __fish_seen_argument --description 'Check whether argument is used'
end
end
for raw_arg in $argv
for raw_arg in $argv_opts $argv
if string match --quiet -- $t $raw_arg
return 0
end

View File

@@ -1,8 +1,23 @@
# This is a helper function for `fish_opt`. It does some basic validation of the arguments.
function __fish_opt_validate_args --no-scope-shadowing
if not set -q _flag_short
or test 1 -ne (string length -- $_flag_short)
printf (_ "%s: The --short flag is required and must be a single character\n") fish_opt >&2
if not set -q _flag_validate && test (count $argv) -ne 0
printf (_ "%s: Extra non-option arguments were provided\n") fish_opt >&2
return 1
else if set -q _flag_validate && test (count $argv) -eq 0
printf (_ "%s: The --validate flag requires subsequent arguments\n") fish_opt >&2
return 1
else if set -q _flag_validate && not set -q _flag_multiple_vals && not set -q _flag_optional_val && not set -q _flag_required_val
printf (_ "%s: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag\n") fish_opt >&2
return 1
else if set -q _flag_short && test 1 -ne (string length -- $_flag_short)
printf (_ "%s: The --short flag must be a single character\n") fish_opt >&2
return 1
else if not set -q _flag_short && not set -q _flag_long
set -S _flag_short >&2
printf (_ "%s: Either the --short or --long flag must be provided\n") fish_opt >&2
return 1
else if set -q _flag_long_only && not set -q _flag_long
printf (_ "%s: The --long-only flag requires the --long flag\n") fish_opt >&2
return 1
end
@@ -11,9 +26,8 @@ end
# The `fish_opt` command.
function fish_opt -d 'Produce an option specification suitable for use with `argparse`.'
set -l options h/help 's/short=' 'l/long=' o/optional-val r/required-val
set options $options L-long-only M-multiple-vals
argparse -n fish_opt --max-args=0 --exclusive=r,o --exclusive=M,o $options -- $argv
set -l options h/help 's/short=' 'l/long=' d/delete o/optional-val r/required-val m/multiple-vals long-only v/validate
argparse -n fish_opt --stop-nonopt --exclusive=r,o $options -- $argv
or return
if set -q _flag_help
@@ -21,21 +35,22 @@ function fish_opt -d 'Produce an option specification suitable for use with `arg
return 0
end
__fish_opt_validate_args
__fish_opt_validate_args $argv
or return
set -l opt_spec $_flag_short
if set -q _flag_long
if set -q _flag_long_only
set opt_spec "$opt_spec-"
else
set opt_spec "$opt_spec/"
end
set opt_spec "$opt_spec$_flag_long"
if not set -q _flag_short
set opt_spec "/$_flag_long"
else if not set -q _flag_long
set opt_spec $_flag_short
else if set -q _flag_long_only
set opt_spec "$_flag_short-$_flag_long"
else
set opt_spec "$_flag_short/$_flag_long"
end
if set -q _flag_multiple_vals
if set -q _flag_multiple_vals && set -q _flag_optional_val
set opt_spec "$opt_spec=*"
else if set -q _flag_multiple_vals
set opt_spec "$opt_spec=+"
else if set -q _flag_required_val
set opt_spec "$opt_spec="
@@ -43,5 +58,13 @@ function fish_opt -d 'Produce an option specification suitable for use with `arg
and set opt_spec "$opt_spec=?"
end
if set -q _flag_delete
set opt_spec "$opt_spec&"
end
if set -q _flag_validate
set opt_spec "$opt_spec!$argv"
end
echo $opt_spec
end

View File

@@ -26,8 +26,8 @@
builtins::{
fish_indent, fish_key_reader,
shared::{
BUILTIN_ERR_MISSING, BUILTIN_ERR_UNKNOWN, STATUS_CMD_ERROR, STATUS_CMD_OK,
STATUS_CMD_UNKNOWN,
BUILTIN_ERR_MISSING, BUILTIN_ERR_UNEXP_ARG, BUILTIN_ERR_UNKNOWN, STATUS_CMD_ERROR,
STATUS_CMD_OK, STATUS_CMD_UNKNOWN,
},
},
common::{
@@ -253,7 +253,7 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
const PRINT_DEBUG_CATEGORIES_ARG: char = 2 as char;
const PROFILE_STARTUP_ARG: char = 3 as char;
const SHORT_OPTS: &wstr = L!("+:hPilNnvc:C:p:d:f:D:o:");
const SHORT_OPTS: &wstr = L!("+hPilNnvc:C:p:d:f:D:o:");
const LONG_OPTS: &[WOption<'static>] = &[
wopt(L!("command"), RequiredArgument, 'c'),
wopt(L!("init-command"), RequiredArgument, 'C'),
@@ -359,6 +359,13 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
);
return ControlFlow::Break(1);
}
';' => {
eprintf!(
"%ls\n",
wgettext_fmt!(BUILTIN_ERR_UNEXP_ARG, "fish", args[w.wopt_index - 1])
);
return ControlFlow::Break(1);
}
_ => panic!("unexpected retval from WGetopter"),
}
}

View File

@@ -453,7 +453,7 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
// Note the leading '-' causes wgetopter to return arguments in order, instead of permuting
// them. We need this behavior for compatibility with pre-builtin abbreviations where options
// could be given literally, for example `abbr e emacs -nw`.
const short_options: &wstr = L!("-:ac:f:r:seqgUh");
const short_options: &wstr = L!("-ac:f:r:seqgUh");
const longopts: &[WOption] = &[
wopt(L!("add"), ArgType::NoArgument, 'a'),
@@ -572,6 +572,10 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);

View File

@@ -13,20 +13,6 @@
"%ls: Invalid option spec '%ls' at char '%lc'\n"
);
#[derive(PartialEq)]
enum ArgCardinality {
Optional = -1isize,
None = 0,
Once = 1,
AtLeastOnce = 2,
}
impl Default for ArgCardinality {
fn default() -> Self {
Self::None
}
}
#[derive(Default)]
struct OptionSpec<'args> {
short_flag: char,
@@ -34,7 +20,9 @@ struct OptionSpec<'args> {
validation_command: &'args wstr,
vals: Vec<WString>,
short_flag_valid: bool,
num_allowed: ArgCardinality,
delete: bool,
arg_type: ArgType,
accumulate_args: bool,
num_seen: isize,
}
@@ -43,14 +31,26 @@ fn new(s: char) -> Self {
Self {
short_flag: s,
short_flag_valid: true,
arg_type: ArgType::NoArgument,
accumulate_args: true,
..Default::default()
}
}
}
#[derive(Default, PartialEq)]
enum UnknownHandling {
#[default]
Error,
Ignore,
Move,
}
#[derive(Default)]
struct ArgParseCmdOpts<'args> {
ignore_unknown: bool,
unknown_handling: UnknownHandling,
unknown_arguments: ArgType,
strict_long_opts: bool,
print_help: bool,
stop_nonopt: bool,
min_args: usize,
@@ -58,7 +58,8 @@ struct ArgParseCmdOpts<'args> {
implicit_int_flag: char,
name: WString,
raw_exclusive_flags: Vec<&'args wstr>,
args: Vec<&'args wstr>,
args: Vec<Cow<'args, wstr>>,
args_opts: Vec<Cow<'args, wstr>>,
options: HashMap<char, OptionSpec<'args>>,
long_to_short_flag: HashMap<WString, char>,
exclusive_flag_sets: Vec<Vec<char>>,
@@ -68,17 +69,21 @@ impl ArgParseCmdOpts<'_> {
fn new() -> Self {
Self {
max_args: usize::MAX,
unknown_arguments: ArgType::OptionalArgument,
..Default::default()
}
}
}
const SHORT_OPTIONS: &wstr = L!("+:hn:six:N:X:");
const SHORT_OPTIONS: &wstr = L!("+hn:siuU:x:SN:X:");
const LONG_OPTIONS: &[WOption] = &[
wopt(L!("stop-nonopt"), ArgType::NoArgument, 's'),
wopt(L!("ignore-unknown"), ArgType::NoArgument, 'i'),
wopt(L!("move-unknown"), ArgType::NoArgument, 'u'),
wopt(L!("unknown-arguments"), ArgType::RequiredArgument, 'U'),
wopt(L!("name"), ArgType::RequiredArgument, 'n'),
wopt(L!("exclusive"), ArgType::RequiredArgument, 'x'),
wopt(L!("strict-longopts"), ArgType::NoArgument, 'S'),
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("min-args"), ArgType::RequiredArgument, 'N'),
wopt(L!("max-args"), ArgType::RequiredArgument, 'X'),
@@ -204,7 +209,11 @@ fn parse_flag_modifiers<'args>(
) -> bool {
let mut s = *opt_spec_str;
if opt_spec.short_flag == opts.implicit_int_flag && !s.is_empty() && s.char_at(0) != '!' {
if opt_spec.short_flag == opts.implicit_int_flag
&& !s.is_empty()
&& s.char_at(0) != '!'
&& s.char_at(0) != '&'
{
streams.err.append(wgettext_fmt!(
"%ls: Implicit int short flag '%lc' does not allow modifiers like '%lc'\n",
opts.name,
@@ -216,17 +225,37 @@ fn parse_flag_modifiers<'args>(
if s.char_at(0) == '=' {
s = s.slice_from(1);
opt_spec.num_allowed = match s.char_at(0) {
'?' => ArgCardinality::Optional,
'+' => ArgCardinality::AtLeastOnce,
_ => ArgCardinality::Once,
(opt_spec.arg_type, opt_spec.accumulate_args) = match s.char_at(0) {
'?' => {
s = s.slice_from(1);
(ArgType::OptionalArgument, false)
}
'+' => {
s = s.slice_from(1);
(ArgType::RequiredArgument, true)
}
'*' => {
s = s.slice_from(1);
(ArgType::OptionalArgument, true)
}
_ => (ArgType::RequiredArgument, false),
};
if opt_spec.num_allowed != ArgCardinality::Once {
s = s.slice_from(1);
}
}
if s.char_at(0) == '&' {
opt_spec.delete = true;
s = s.slice_from(1);
}
if s.char_at(0) == '!' {
if opt_spec.arg_type == ArgType::NoArgument {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_INVALID_OPT_SPEC,
opts.name,
option_spec,
s.char_at(0)
));
}
s = s.slice_from(1);
opt_spec.validation_command = s;
// Move cursor to the end so we don't expect a long flag.
@@ -329,10 +358,11 @@ fn parse_option_spec_sep<'args>(
return false;
}
opts.implicit_int_flag = opt_spec.short_flag;
opt_spec.num_allowed = ArgCardinality::Once;
opt_spec.arg_type = ArgType::RequiredArgument;
opt_spec.accumulate_args = false;
i += 1; // the struct is initialized assuming short_flag_valid should be true
}
'!' | '?' | '=' => {
'!' | '?' | '=' | '&' => {
// Try to parse any other flag modifiers
// parse_flag_modifiers assumes opt_spec_str starts where it should, not one earlier
s = s.slice_from(i);
@@ -342,10 +372,12 @@ fn parse_option_spec_sep<'args>(
}
}
_ => {
// No short flag separator and no other modifiers, so this is a long only option.
// No short flag or separator, and no other modifiers, so this is a long only option.
// Since getopt needs a wchar, we have a counter that we count up.
opt_spec.short_flag_valid = false;
i -= 1;
if s.char_at(i - 1) != '/' {
i -= 1
}
opt_spec.short_flag = char::from_u32(*counter).unwrap();
*counter += 1;
}
@@ -370,7 +402,8 @@ fn parse_option_spec<'args>(
}
let mut s = option_spec;
if !fish_iswalnum(s.char_at(0)) && s.char_at(0) != '#' {
if !fish_iswalnum(s.char_at(0)) && s.char_at(0) != '#' && !(s.char_at(0) == '/' && s.len() > 1)
{
streams.err.append(wgettext_fmt!(
"%ls: Short flag '%lc' invalid, must be alphanum or '#'\n",
opts.name,
@@ -491,12 +524,47 @@ fn parse_cmd_opts<'args>(
let mut args_read = Vec::with_capacity(args.len());
args_read.extend_from_slice(args);
let mut seen_unknown_arguments = false;
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, args);
while let Some(c) = w.next_opt() {
match c {
'n' => opts.name = w.woptarg.unwrap().to_owned(),
's' => opts.stop_nonopt = true,
'i' => opts.ignore_unknown = true,
'i' | 'u' => {
if opts.unknown_handling != UnknownHandling::Error {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
"--ignore-unknown",
"--move-unknown"
));
return Err(STATUS_INVALID_ARGS);
};
opts.unknown_handling = if c == 'i' {
UnknownHandling::Ignore
} else {
UnknownHandling::Move
}
}
'U' => {
seen_unknown_arguments = true;
let kind = w.woptarg.unwrap();
opts.unknown_arguments = if kind == L!("optional") {
ArgType::OptionalArgument
} else if kind == L!("required") {
ArgType::RequiredArgument
} else if kind == L!("none") {
ArgType::NoArgument
} else {
streams.err.append(wgettext_fmt!(
"%ls: Invalid --unknown-arguments value '%ls'\n",
cmd,
kind
));
return Err(STATUS_INVALID_ARGS);
}
}
'S' => opts.strict_long_opts = true,
// Just save the raw string here. Later, when we have all the short and long flag
// definitions we'll parse these strings into a more useful data structure.
'x' => opts.raw_exclusive_flags.push(w.woptarg.unwrap()),
@@ -539,6 +607,16 @@ fn parse_cmd_opts<'args>(
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
args[w.wopt_index - 1],
/* print_hints */ false,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
@@ -547,6 +625,11 @@ fn parse_cmd_opts<'args>(
}
}
// Imply --unknown-arguments implies --move-unknown, unless --ignore-unknown was given
if seen_unknown_arguments && opts.unknown_handling == UnknownHandling::Error {
opts.unknown_handling = UnknownHandling::Move;
}
if opts.print_help {
return Ok(SUCCESS);
}
@@ -585,24 +668,20 @@ fn populate_option_strings<'args>(
short_options.push(opt_spec.short_flag);
}
let arg_type = match opt_spec.num_allowed {
ArgCardinality::Optional => {
if opt_spec.short_flag_valid {
short_options.push_str("::");
}
ArgType::OptionalArgument
}
ArgCardinality::Once | ArgCardinality::AtLeastOnce => {
if opt_spec.short_flag_valid {
short_options.push_str(":");
}
ArgType::RequiredArgument
}
ArgCardinality::None => ArgType::NoArgument,
};
if opt_spec.short_flag_valid {
match opt_spec.arg_type {
ArgType::OptionalArgument => short_options.push_str("::"),
ArgType::RequiredArgument => short_options.push_str(":"),
ArgType::NoArgument => {}
};
}
if !opt_spec.long_flag.is_empty() {
long_options.push(wopt(opt_spec.long_flag, arg_type, opt_spec.short_flag));
long_options.push(wopt(
opt_spec.long_flag,
opt_spec.arg_type,
opt_spec.short_flag,
));
}
}
}
@@ -679,6 +758,10 @@ fn validate_and_store_implicit_int<'args>(
streams: &mut IoStreams,
) -> BuiltinResult {
let opt_spec = opts.options.get_mut(&opts.implicit_int_flag).unwrap();
if opt_spec.delete {
// No need to call delete_flag, as w.argv_opts.last will be of form -<number>
w.argv_opts.pop().unwrap();
}
validate_arg(parser, &opts.name, opt_spec, is_long_flag, val, streams)?;
// It's a valid integer so store it and return success.
@@ -690,21 +773,92 @@ fn validate_and_store_implicit_int<'args>(
Ok(SUCCESS)
}
/// Delete the most recently matched flag, is_long_flag should be true if the flag was given with a
/// long flag name (even if it was given with a single - and not two). The returned value is the
/// deleted flag and its value (unless the value was given as a seperate argument)
fn delete_flag<'args>(w: &mut WGetopter<'_, 'args, '_>, is_long_flag: bool) -> Cow<'args, wstr> {
// Does the option have a value that was a seperate argument to the option name itself? (e.g.
// w.argv_opts ends in --<long_flag> <value> or -<other-short-options>...<short_flag> <value>)
let separate_value = w
.woptarg
.zip(w.argv_opts.last())
.is_some_and(|(value, last)| value == *last);
if separate_value {
// Remove the value
w.argv_opts.pop();
// There can't be any short options between the flag and its value
assert!(w.remaining_text.is_empty());
}
// Remove the last option (we may have to put part of it back if it had other short options in
// it)
let opt_arg = w.argv_opts.pop().unwrap();
assert!(opt_arg.starts_with("-"));
if is_long_flag {
// opt_arg will be of form --<long-flag>, except there may only be one -, and <long-flag>
// may be abbreviated
assert!(w.remaining_text.is_empty());
// There will be no short options, so we're done
return opt_arg;
}
// Otherwise, it's a short option so opt_arg will be of form:
// -<previous-short-ops>...<short-flag><more-short-ops>...
// -<previous-short-ops>...<short-flag><value>, or
// -<previous-short-ops>...<short-flag>
let more_opts = w.remaining_text;
let value = if separate_value {
L!("")
} else {
w.woptarg.unwrap_or_default()
};
assert!(more_opts.is_empty() || value.is_empty()); // Both can't be present
assert!(opt_arg.ends_with(more_opts));
assert!(opt_arg.ends_with(value));
// The length of <previous-short-ops>... (the -2 is for the '-' and <short-flag>)
let previous_opts = opt_arg.len() - (more_opts.len() + value.len()) - 2;
if previous_opts == 0 && more_opts.is_empty() {
// opt_arg will be of form -<short-flag> or -<short-flag><value>
// (i.e. there are no other options that we need to add back to w.argv_opts)
return opt_arg;
}
// Set opt_arg_with to be opt_arg minus the <previous-short-ops>... (i.e. -<short-flag><value>)
// (the +1 is to skip over the leading '-', and the +2 make it a one character slice)
let opt_arg_with = L!("-").to_owned() + &opt_arg[previous_opts + 1..previous_opts + 2] + value;
// Set opt_arg_without to to be opt_arg minus <short-flag><value> (i.e.
// -<previous-short-opts>...<more-short-opts>). (the +1 is to skip over the leading '-')
let opt_arg_without = opt_arg[..previous_opts + 1].to_owned() + more_opts;
assert!(opt_arg.len() > 1); // There should be at least one short opt
// Put the version without <short-flag> back, and return the version with it
w.argv_opts.push(opt_arg_without.into());
Cow::Owned(opt_arg_with)
}
fn handle_flag<'args>(
parser: &Parser,
opts: &mut ArgParseCmdOpts<'args>,
opt: char,
is_long_flag: bool,
woptarg: Option<&'args wstr>,
w: &mut WGetopter<'_, 'args, '_>,
streams: &mut IoStreams,
) -> BuiltinResult {
let opt_spec = opts.options.get_mut(&opt).unwrap();
if opt_spec.delete {
delete_flag(w, is_long_flag);
}
opt_spec.num_seen += 1;
if opt_spec.num_allowed == ArgCardinality::None {
if opt_spec.arg_type == ArgType::NoArgument {
// It's a boolean flag. Save the flag we saw since it might be useful to know if the
// short or long flag was given.
assert!(woptarg.is_none());
assert!(opt_spec.accumulate_args);
assert!(w.woptarg.is_none());
let s = if is_long_flag {
WString::from("--") + opt_spec.long_flag
} else {
@@ -714,22 +868,23 @@ fn handle_flag<'args>(
return Ok(SUCCESS);
}
if let Some(woptarg) = woptarg {
if let Some(woptarg) = w.woptarg {
validate_arg(parser, &opts.name, opt_spec, is_long_flag, woptarg, streams)?;
}
match opt_spec.num_allowed {
ArgCardinality::Optional | ArgCardinality::Once => {
// We're depending on `next_opt()` to report that a mandatory value is missing if
// `opt_spec->num_allowed == 1` and thus return ':' so that we don't take this branch if
// the mandatory arg is missing.
opt_spec.vals.clear();
if let Some(arg) = woptarg {
opt_spec.vals.push(arg.into());
}
if opt_spec.accumulate_args {
if let Some(arg) = w.woptarg {
opt_spec.vals.push(arg.into());
} else {
opt_spec.vals.push(WString::new());
}
_ => {
opt_spec.vals.push(woptarg.unwrap().into());
} else {
// We're depending on `next_opt()` to report that a mandatory value is missing if
// `opt_spec->arg_type == ArgType::RequiredArgument` and thus return ':' so that we don't
// take this branch if the mandatory arg is missing.
opt_spec.vals.clear();
if let Some(arg) = w.woptarg {
opt_spec.vals.push(arg.into());
}
}
@@ -749,14 +904,15 @@ fn argparse_parse_flags<'args>(
// "+" means stop at nonopt, "-" means give nonoptions the option character code `1`, and don't
// reorder.
let mut short_options = WString::from(if opts.stop_nonopt { L!("+:") } else { L!("-:") });
let mut short_options = WString::from(if opts.stop_nonopt { L!("+") } else { L!("-") });
let mut long_options = vec![];
populate_option_strings(opts, &mut short_options, &mut long_options);
let mut w = WGetopter::new(&short_options, &long_options, args);
w.strict_long_opts = opts.strict_long_opts;
while let Some((opt, longopt_idx)) = w.next_opt_indexed() {
let is_long_flag = longopt_idx.is_some();
let retval: BuiltinResult = match opt {
match opt {
':' => {
builtin_missing_argument(
parser,
@@ -765,7 +921,17 @@ fn argparse_parse_flags<'args>(
args_read[w.wopt_index - 1],
false,
);
Err(STATUS_INVALID_ARGS)
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
&opts.name,
args_read[w.wopt_index - 1],
false,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
// It's not a recognized flag. See if it's an implicit int flag.
@@ -779,27 +945,121 @@ fn argparse_parse_flags<'args>(
&mut w,
is_long_flag,
streams,
)
} else if !opts.ignore_unknown {
)?;
} else if opts.unknown_handling == UnknownHandling::Error {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_UNKNOWN,
opts.name,
args_read[w.wopt_index - 1]
));
Err(STATUS_INVALID_ARGS)
return Err(STATUS_INVALID_ARGS);
} else {
// The option is unknown, so there's no long opt index it could have used
assert!(!is_long_flag);
// arg_contents already skipped over the first '-'
let is_long_flag = arg_contents.starts_with("-");
if is_long_flag {
// For some reason, wgetopt parses unknown long options as if they where a
// short '-' flag, followed by the long flag name, interpreted either as the
// value for '-' or as remaining short options, so this fixes that
let rest = if let Some(value) = w.woptarg {
assert!(w.remaining_text.is_empty());
value
} else {
w.remaining_text
};
// The arguments was of form --<long-flag>=<value>, so extract out <value>
if let Some(i) = rest.find_char('=') {
w.woptarg = Some(&rest[i + 1..]);
}
// Ensure w.remaining_text is not misinterpreted as the value of the flag,
// or as remaining short options
w.remaining_text = L!("");
}
// Any unrecognized option is put back if ignore_unknown is used.
// This allows reusing the same argv in multiple argparse calls,
// or just ignoring the error (e.g. in completions).
opts.args.push(args_read[w.wopt_index - 1]);
// First we consume any remaining text as if it was the option's argument
if opts.unknown_arguments != ArgType::NoArgument && !w.remaining_text.is_empty()
{
assert!(w.woptarg.is_none()); // Both don't make sense
w.woptarg = Some(w.remaining_text);
// Explain to wgetopt that we want to skip to the next arg,
// because we can't handle this opt group.
w.remaining_text = L!("");
}
// If unknown options require arguments, but there weren't any worked out above,
// take the next element of w.argv as the argument
let separate_value = if opts.unknown_arguments == ArgType::RequiredArgument
&& w.woptarg.is_none()
{
if w.wopt_index < w.argv.len() {
w.wopt_index += 1; // Tell wgetop to skip over the options value
Some(w.argv[w.wopt_index - 1])
} else {
// the option is at the end of argv, so it has no argument
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_MISSING,
opts.name,
args_read[w.wopt_index - 1]
));
return Err(STATUS_INVALID_ARGS);
}
} else {
None
};
// If the option uses the long flag syntax with a value (i.e. --<flag>=<value>)
if opts.unknown_arguments == ArgType::NoArgument
&& is_long_flag
&& arg_contents.contains('=')
{
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_UNEXP_ARG,
opts.name,
args_read[w.wopt_index - 1]
));
return Err(STATUS_INVALID_ARGS);
}
if opts.unknown_handling == UnknownHandling::Ignore {
// Now by calling delete_flag we ensure that if the unknown flag is
// precceded by known flags, the known flags are kept in $argv_opts, and not
// added to $argv. (any argument to the option is also returned in
// unknown_flag, and removed from opts.argv_opts) Now by calling delete_flag
// we ensure that if the unknown flag is precceded by known flags, the known
// flags are kept in $argv_opts, and not added to $argv. (any argument to
// the option is also returned in unknown_flag, and removed from
// opts.argv_opts)
let unknown_flag = delete_flag(&mut w, is_long_flag);
opts.args.push(unknown_flag);
// If the value of the argument was the next element of the input arguments,
// record it to be stored in the new $argv value
if let Some(value) = separate_value {
opts.args.push(Cow::Borrowed(value));
}
} else {
assert!(opts.unknown_handling == UnknownHandling::Move);
// w.argv_opts will already contain the option and its value, unless the
// value was given as a seperate argument
if let Some(value) = separate_value {
w.argv_opts.push(Cow::Borrowed(value));
}
}
// Make wgetopt keep reading this argument for more options
if !w.remaining_text.is_empty() {
w.wopt_index -= 1;
}
// Work around weirdness with wgetopt, which crashes if we `continue` here.
if w.wopt_index == argc {
break;
}
// Explain to wgetopt that we want to skip to the next arg,
// because we can't handle this opt group.
w.remaining_text = L!("");
Ok(SUCCESS)
}
}
NON_OPTION_CHAR => {
@@ -808,14 +1068,16 @@ fn argparse_parse_flags<'args>(
// otherwise we'd get ignored options first and normal arguments later.
// E.g. `argparse -i -- -t tango -w` needs to keep `-t tango -w` in $argv, not `-t -w
// tango`.
opts.args.push(args_read[w.wopt_index - 1]);
opts.args.push(Cow::Borrowed(args_read[w.wopt_index - 1]));
continue;
}
// It's a recognized flag.
_ => handle_flag(parser, opts, opt, is_long_flag, w.woptarg, streams),
};
retval?;
_ => {
handle_flag(parser, opts, opt, is_long_flag, &mut w, streams)?;
}
}
}
opts.args_opts = w.argv_opts;
*optind = w.wopt_index;
return Ok(SUCCESS);
@@ -840,7 +1102,8 @@ fn argparse_parse_args<'args>(
check_for_mutually_exclusive_flags(opts, streams)?;
opts.args.extend_from_slice(&args[optind..]);
opts.args
.extend(args[optind..].iter().map(|&s| Cow::Borrowed(s)));
Ok(SUCCESS)
}
@@ -875,7 +1138,7 @@ fn check_min_max_args_constraints(
}
/// Put the result of parsing the supplied args into the caller environment as local vars.
fn set_argparse_result_vars(vars: &EnvStack, opts: &ArgParseCmdOpts) {
fn set_argparse_result_vars(vars: &EnvStack, opts: ArgParseCmdOpts) {
for opt_spec in opts.options.values() {
if opt_spec.num_seen == 0 {
continue;
@@ -899,8 +1162,10 @@ fn set_argparse_result_vars(vars: &EnvStack, opts: &ArgParseCmdOpts) {
}
}
let args = opts.args.iter().map(|&s| s.to_owned()).collect();
let args = opts.args.into_iter().map(|s| s.into_owned()).collect();
vars.set(L!("argv"), EnvMode::LOCAL, args);
let args_opts = opts.args_opts.into_iter().map(|s| s.into_owned()).collect();
vars.set(L!("argv_opts"), EnvMode::LOCAL, args_opts);
}
/// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this
@@ -948,7 +1213,7 @@ pub fn argparse(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
check_min_max_args_constraints(&opts, streams)?;
set_argparse_result_vars(parser.vars(), &opts);
set_argparse_result_vars(parser.vars(), opts);
Ok(SUCCESS)
}

View File

@@ -407,7 +407,7 @@ fn parse_cmd_opts(
streams: &mut IoStreams,
) -> BuiltinResult {
let cmd = argv[0];
let short_options = L!(":aehkKfM:Lm:s");
let short_options = L!("aehkKfM:Lm:s");
const long_options: &[WOption] = &[
wopt(L!("all"), NoArgument, 'a'),
wopt(L!("erase"), NoArgument, 'e'),
@@ -477,6 +477,10 @@ fn parse_cmd_opts(
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);

View File

@@ -30,7 +30,7 @@ fn parse_options(
) -> Result<(Options, usize), ErrorCode> {
let cmd = args[0];
const SHORT_OPTS: &wstr = L!(":eghl");
const SHORT_OPTS: &wstr = L!("eghl");
const LONG_OPTS: &[WOption] = &[
wopt(L!("erase"), ArgType::NoArgument, 'e'),
wopt(L!("local"), ArgType::NoArgument, 'l'),
@@ -59,6 +59,10 @@ fn parse_options(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);

View File

@@ -12,7 +12,7 @@ pub fn r#builtin(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
let print_hints = false;
let mut opts: builtin_cmd_opts_t = Default::default();
const shortopts: &wstr = L!(":hnq");
const shortopts: &wstr = L!("hnq");
const longopts: &[WOption] = &[
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("names"), ArgType::NoArgument, 'n'),
@@ -32,6 +32,16 @@ pub fn r#builtin(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);

View File

@@ -14,7 +14,7 @@ pub fn r#command(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
let print_hints = false;
let mut opts: command_cmd_opts_t = Default::default();
const shortopts: &wstr = L!(":hasqv");
const shortopts: &wstr = L!("hasqv");
const longopts: &[WOption] = &[
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("all"), ArgType::NoArgument, 'a'),
@@ -39,6 +39,16 @@ pub fn r#command(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);

View File

@@ -269,7 +269,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let mut override_buffer = None;
const short_options: &wstr = L!(":abijpctfxorhI:CBELSsP");
const short_options: &wstr = L!("abijpctfxorhI:CBELSsP");
let long_options: &[WOption] = &[
wopt(L!("append"), ArgType::NoArgument, 'a'),
wopt(L!("insert"), ArgType::NoArgument, 'i'),
@@ -355,6 +355,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
builtin_missing_argument(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);

View File

@@ -239,7 +239,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
let mut preserve_order = false;
let mut unescape_output = true;
const short_options: &wstr = L!(":a:c:p:s:l:o:d:fFrxeuAn:C::w:hk");
const short_options: &wstr = L!("a:c:p:s:l:o:d:fFrxeuAn:C::w:hk");
const long_options: &[WOption] = &[
wopt(L!("exclusive"), ArgType::NoArgument, 'x'),
wopt(L!("no-files"), ArgType::NoArgument, 'f'),
@@ -382,6 +382,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);

View File

@@ -14,7 +14,7 @@ fn parse_options(
) -> Result<(Options, usize), ErrorCode> {
let cmd = args[0];
const SHORT_OPTS: &wstr = L!("+:hi");
const SHORT_OPTS: &wstr = L!("+hi");
const LONG_OPTS: &[WOption] = &[
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("index"), ArgType::NoArgument, 'i'),
@@ -31,6 +31,10 @@ fn parse_options(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);

View File

@@ -29,7 +29,7 @@ fn parse_options(
return Err(STATUS_INVALID_ARGS);
};
const SHORT_OPTS: &wstr = L!("+:Eens");
const SHORT_OPTS: &wstr = L!("+Eens");
const LONG_OPTS: &[WOption] = &[];
let mut opts = Options::default();
@@ -48,6 +48,9 @@ fn parse_options(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
panic!("unexpected option arguments are only possible with long options")
}
'?' => {
return Ok((oldopts, w.wopt_index - 1));
}

View File

@@ -212,6 +212,14 @@ fn parse_flags(
'V' => {
*verbose = true;
}
';' => {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_UNEXP_ARG,
"fish_key_reader",
w.argv[w.wopt_index - 1]
));
return ControlFlow::Break(Err(STATUS_CMD_ERROR));
}
'?' => {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_UNKNOWN,

View File

@@ -40,7 +40,7 @@ fn default() -> Self {
// This command is atypical in using the "-" (RETURN_IN_ORDER) option for flag parsing.
// This is needed due to the semantics of the -a/--argument-names flag.
const SHORT_OPTIONS: &wstr = L!("-:a:d:e:hj:p:s:v:w:SV:");
const SHORT_OPTIONS: &wstr = L!("-a:d:e:hj:p:s:v:w:SV:");
#[rustfmt::skip]
const LONG_OPTIONS: &[WOption] = &[
wopt(L!("description"), ArgType::RequiredArgument, 'd'),
@@ -219,6 +219,16 @@ fn parse_cmd_opts(
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return STATUS_INVALID_ARGS;
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
argv[w.wopt_index - 1],
print_hints,
);
return STATUS_INVALID_ARGS;
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return STATUS_INVALID_ARGS;

View File

@@ -49,7 +49,7 @@ fn default() -> Self {
const NO_METADATA_SHORT: char = 2 as char;
const SHORT_OPTIONS: &wstr = L!(":Ht:Dacd:ehnqv");
const SHORT_OPTIONS: &wstr = L!("Ht:Dacd:ehnqv");
#[rustfmt::skip]
const LONG_OPTIONS: &[WOption] = &[
wopt(L!("erase"), ArgType::NoArgument, 'e'),
@@ -101,6 +101,16 @@ fn parse_cmd_opts<'args>(
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);

View File

@@ -66,7 +66,7 @@ struct HistoryCmdOpts {
/// the non-flag subcommand form. While many of these flags are deprecated they must be
/// supported at least until fish 3.0 and possibly longer to avoid breaking everyones
/// config.fish and other scripts.
const short_options: &wstr = L!(":CRcehmn:pt::z");
const short_options: &wstr = L!("CRcehmn:pt::z");
const longopts: &[WOption] = &[
wopt(L!("prefix"), ArgType::NoArgument, 'p'),
wopt(L!("contains"), ArgType::NoArgument, 'c'),
@@ -211,6 +211,10 @@ fn parse_cmd_opts(
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
// Try to parse it as a number; e.g., "-123".
match fish_wcstol(&w.argv[w.wopt_index - 1][1..]) {

View File

@@ -117,7 +117,7 @@ fn builtin_jobs_print(j: &Job, mode: JobsPrintMode, header: bool, streams: &mut
};
}
const SHORT_OPTIONS: &wstr = L!(":cghlpq");
const SHORT_OPTIONS: &wstr = L!("cghlpq");
const LONG_OPTIONS: &[WOption] = &[
wopt(L!("command"), ArgType::NoArgument, 'c'),
wopt(L!("group"), ArgType::NoArgument, 'g'),
@@ -166,6 +166,10 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);

View File

@@ -38,7 +38,7 @@ fn parse_cmd_opts(
// This command is atypical in using the "+" (REQUIRE_ORDER) option for flag parsing.
// This is needed because of the minus, `-`, operator in math expressions.
const SHORT_OPTS: &wstr = L!("+:hs:b:m:");
const SHORT_OPTS: &wstr = L!("+hs:b:m:");
const LONG_OPTS: &[WOption] = &[
wopt(L!("scale"), ArgType::RequiredArgument, 's'),
wopt(L!("base"), ArgType::RequiredArgument, 'b'),
@@ -120,6 +120,16 @@ fn parse_cmd_opts(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
args[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
// For most commands this is an error. We ignore it because a math expression
// can begin with a minus sign.

View File

@@ -178,7 +178,7 @@ fn path_out(streams: &mut IoStreams, opts: &Options<'_>, s: impl AsRef<wstr>) {
fn construct_short_opts(opts: &Options) -> WString {
// All commands accept -z, -Z and -q
let mut short_opts = WString::from(":zZq");
let mut short_opts = WString::from("zZq");
if opts.perms_valid {
short_opts += L!("p:");
short_opts += L!("rwx");
@@ -247,6 +247,17 @@ fn parse_opts<'args>(
builtin_missing_argument(parser, streams, cmd, args_read[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
streams.err.append(L!("path ")); // clone of string_error
builtin_unexpected_argument(
parser,
streams,
cmd,
args_read[w.wopt_index - 1],
false,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
path_unknown_option(parser, streams, cmd, args_read[w.wopt_index - 1]);
return Err(STATUS_INVALID_ARGS);

View File

@@ -25,6 +25,10 @@ pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Buil
builtin_print_help(parser, streams, cmd);
return Ok(SUCCESS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, argv[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);

View File

@@ -14,7 +14,7 @@ pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
let argc = argv.len();
let print_hints = false;
const shortopts: &wstr = L!("+:h");
const shortopts: &wstr = L!("+h");
const longopts: &[WOption] = &[wopt(L!("help"), ArgType::NoArgument, 'h')];
let mut w = WGetopter::new(shortopts, longopts, argv);
@@ -29,6 +29,16 @@ pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);

View File

@@ -63,7 +63,7 @@ fn new() -> Self {
}
}
const SHORT_OPTIONS: &wstr = L!(":ac:d:fghiLln:p:sStuxzP:UR:L");
const SHORT_OPTIONS: &wstr = L!("ac:d:fghiLln:p:sStuxzP:UR:L");
const LONG_OPTIONS: &[WOption] = &[
wopt(L!("array"), ArgType::NoArgument, 'a'),
wopt(L!("command"), ArgType::RequiredArgument, 'c'),
@@ -185,6 +185,10 @@ fn parse_cmd_opts(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, args[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);

View File

@@ -15,7 +15,7 @@ struct Options {
no_symlinks: bool,
}
const short_options: &wstr = L!("+:hs");
const short_options: &wstr = L!("+hs");
const long_options: &[WOption] = &[
wopt(L!("no-symlinks"), NoArgument, 's'),
wopt(L!("help"), NoArgument, 'h'),
@@ -40,6 +40,10 @@ fn parse_options(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);

View File

@@ -16,7 +16,7 @@ fn parse_options(
) -> ControlFlow<ErrorCode, (Options, usize)> {
let cmd = args[0];
const SHORT_OPTS: &wstr = L!(":h");
const SHORT_OPTS: &wstr = L!("h");
const LONG_OPTS: &[WOption] = &[wopt(L!("help"), ArgType::NoArgument, 'h')];
let mut opts = Options::default();
@@ -30,6 +30,10 @@ fn parse_options(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
return ControlFlow::Break(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
return ControlFlow::Break(STATUS_INVALID_ARGS);
}
'?' => {
// We would normally invoke builtin_unknown_option() and return an error.
// But for this command we want to let it try and parse the value as a negative

View File

@@ -108,7 +108,7 @@ fn parse(
// Variables used for parsing the argument list. This command is atypical in using the "+"
// (REQUIRE_ORDER) option for flag parsing. This is not typical of most fish commands. It means
// we stop scanning for flags when the first non-flag argument is seen.
const SHORT_OPTS: &wstr = L!("+:LSUaefghlnpqux");
const SHORT_OPTS: &wstr = L!("+LSUaefghlnpqux");
const LONG_OPTS: &[WOption] = &[
wopt(L!("export"), NoArgument, 'x'),
wopt(L!("global"), NoArgument, 'g'),
@@ -167,6 +167,16 @@ fn parse(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
args[w.wopt_index - 1],
false,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
// Specifically detect `set -o` because people might be bringing over bashisms.
let optind = w.wopt_index;

View File

@@ -86,6 +86,16 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
// In future we change both to actually print an error.
return Err(STATUS_INVALID_ARGS);
}
Err(UnexpectedOptArg(option_index)) => {
builtin_unexpected_argument(
parser,
streams,
L!("set_color"),
argv[option_index],
true, /* print_hints */
);
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownColor(arg)) => {
streams
.err

View File

@@ -27,6 +27,10 @@
pub BUILTIN_ERR_MISSING
"%ls: %ls: option requires an argument\n"
/// Error message on unexpected argument.
pub BUILTIN_ERR_UNEXP_ARG
"%ls: %ls: option does not take an argument\n"
/// Error message on missing man page.
pub BUILTIN_ERR_MISSING_HELP
"fish: %ls: missing man page\nDocumentation may not be installed.\n`help %ls` will show an online version\n"
@@ -694,6 +698,22 @@ pub fn builtin_missing_argument(
}
}
/// Perform error reporting for encounter with an extra argument.
pub fn builtin_unexpected_argument(
parser: &Parser,
streams: &mut IoStreams,
cmd: &wstr,
opt: &wstr,
print_hints: bool, /*=true*/
) {
streams
.err
.append(wgettext_fmt!(BUILTIN_ERR_UNEXP_ARG, cmd, opt));
if print_hints {
builtin_print_error_trailer(parser, streams.err, cmd);
}
}
/// Print the backtrace and call for help that we use at the end of error messages.
pub fn builtin_print_error_trailer(parser: &Parser, b: &mut OutputStream, cmd: &wstr) {
b.push('\n');
@@ -736,7 +756,7 @@ pub fn parse(
let cmd = args[0];
let print_hints = true;
const shortopts: &wstr = L!("+:h");
const shortopts: &wstr = L!("+h");
const longopts: &[WOption] = &[wopt(L!("help"), ArgType::NoArgument, 'h')];
let mut print_help = false;
@@ -756,6 +776,16 @@ pub fn parse(
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
args[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(
parser,

View File

@@ -150,7 +150,7 @@ fn default() -> Self {
const IS_NO_JOB_CTRL_SHORT: char = '\x04';
const IS_INTERACTIVE_READ_SHORT: char = '\x05';
const SHORT_OPTIONS: &wstr = L!(":L:cbilfnhj:t");
const SHORT_OPTIONS: &wstr = L!("L:cbilfnhj:t");
const LONG_OPTIONS: &[WOption] = &[
wopt(L!("help"), NoArgument, 'h'),
wopt(L!("current-filename"), NoArgument, 'f'),
@@ -304,6 +304,10 @@ fn parse_cmd_opts(
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);

View File

@@ -71,6 +71,17 @@ fn parse_opts(
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
streams.err.append(L!("string ")); // clone of string_error
builtin_unexpected_argument(
parser,
streams,
cmd,
args_read[w.wopt_index - 1],
false,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
string_unknown_option(parser, streams, cmd, args_read[w.wopt_index - 1]);
return Err(STATUS_INVALID_ARGS);

View File

@@ -11,7 +11,7 @@ impl StringSubCommand<'_> for Collect {
wopt(L!("allow-empty"), NoArgument, 'a'),
wopt(L!("no-trim-newlines"), NoArgument, 'N'),
];
const SHORT_OPTIONS: &'static wstr = L!(":Na");
const SHORT_OPTIONS: &'static wstr = L!("Na");
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -12,7 +12,7 @@ impl StringSubCommand<'_> for Escape {
wopt(L!("no-quoted"), NoArgument, 'n'),
wopt(L!("style"), RequiredArgument, NON_OPTION_CHAR),
];
const SHORT_OPTIONS: &'static wstr = L!(":n");
const SHORT_OPTIONS: &'static wstr = L!("n");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -23,7 +23,7 @@ impl<'args> StringSubCommand<'args> for Join<'args> {
wopt(L!("quiet"), NoArgument, 'q'),
wopt(L!("no-empty"), NoArgument, 'n'),
];
const SHORT_OPTIONS: &'static wstr = L!(":qn");
const SHORT_OPTIONS: &'static wstr = L!("qn");
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -11,7 +11,7 @@ impl StringSubCommand<'_> for Length {
wopt(L!("quiet"), NoArgument, 'q'),
wopt(L!("visible"), NoArgument, 'V'),
];
const SHORT_OPTIONS: &'static wstr = L!(":qV");
const SHORT_OPTIONS: &'static wstr = L!("qV");
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -34,7 +34,7 @@ impl<'args> StringSubCommand<'args> for Match<'args> {
wopt(L!("index"), NoArgument, 'n'),
wopt(L!("max-matches"), RequiredArgument, 'm'),
];
const SHORT_OPTIONS: &'static wstr = L!(":aegivqrnm:");
const SHORT_OPTIONS: &'static wstr = L!("aegivqrnm:");
fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -26,7 +26,7 @@ impl StringSubCommand<'_> for Pad {
wopt(L!("right"), NoArgument, 'r'),
wopt(L!("width"), RequiredArgument, 'w'),
];
const SHORT_OPTIONS: &'static wstr = L!(":c:rw:");
const SHORT_OPTIONS: &'static wstr = L!("c:rw:");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -15,7 +15,7 @@ impl StringSubCommand<'_> for Repeat {
wopt(L!("quiet"), NoArgument, 'q'),
wopt(L!("no-newline"), NoArgument, 'N'),
];
const SHORT_OPTIONS: &'static wstr = L!(":n:m:qN");
const SHORT_OPTIONS: &'static wstr = L!("n:m:qN");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -26,7 +26,7 @@ impl<'args> StringSubCommand<'args> for Replace<'args> {
wopt(L!("regex"), NoArgument, 'r'),
wopt(L!("max-matches"), RequiredArgument, 'm'),
];
const SHORT_OPTIONS: &'static wstr = L!(":afiqrm:");
const SHORT_OPTIONS: &'static wstr = L!("afiqrm:");
fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -32,7 +32,7 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
wopt(L!("left"), NoArgument, 'l'),
wopt(L!("quiet"), NoArgument, 'q'),
];
const SHORT_OPTIONS: &'static wstr = L!(":c:m:Nlq");
const SHORT_OPTIONS: &'static wstr = L!("c:m:Nlq");
fn parse_opt(
&mut self,

View File

@@ -110,7 +110,7 @@ impl<'args> StringSubCommand<'args> for Split<'args> {
wopt(L!("fields"), RequiredArgument, 'f'),
wopt(L!("allow-empty"), NoArgument, 'a'),
];
const SHORT_OPTIONS: &'static wstr = L!(":qrm:nf:a");
const SHORT_OPTIONS: &'static wstr = L!("qrm:nf:a");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -17,7 +17,7 @@ impl StringSubCommand<'_> for Sub {
wopt(L!("end"), RequiredArgument, 'e'),
wopt(L!("quiet"), NoArgument, 'q'),
];
const SHORT_OPTIONS: &'static wstr = L!(":l:qs:e:");
const SHORT_OPTIONS: &'static wstr = L!("l:qs:e:");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -7,7 +7,7 @@ pub struct Transform {
impl StringSubCommand<'_> for Transform {
const LONG_OPTIONS: &'static [WOption<'static>] = &[wopt(L!("quiet"), NoArgument, 'q')];
const SHORT_OPTIONS: &'static wstr = L!(":q");
const SHORT_OPTIONS: &'static wstr = L!("q");
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
match c {
'q' => self.quiet = true,

View File

@@ -26,7 +26,7 @@ impl<'args> StringSubCommand<'args> for Trim<'args> {
wopt(L!("right"), NoArgument, 'r'),
wopt(L!("quiet"), NoArgument, 'q'),
];
const SHORT_OPTIONS: &'static wstr = L!(":c:lrq");
const SHORT_OPTIONS: &'static wstr = L!("c:lrq");
fn parse_opt(
&mut self,

View File

@@ -14,7 +14,7 @@ impl StringSubCommand<'_> for Unescape {
wopt(L!("no-quoted"), NoArgument, 'n'),
wopt(L!("style"), RequiredArgument, NON_OPTION_CHAR),
];
const SHORT_OPTIONS: &'static wstr = L!(":n");
const SHORT_OPTIONS: &'static wstr = L!("n");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
match c {

View File

@@ -23,7 +23,7 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
let print_hints = false;
let mut opts: type_cmd_opts_t = Default::default();
const shortopts: &wstr = L!(":hasftpPq");
const shortopts: &wstr = L!("hasftpPq");
const longopts: &[WOption] = &[
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("all"), ArgType::NoArgument, 'a'),
@@ -54,6 +54,16 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);

View File

@@ -171,7 +171,7 @@ fn default() -> Self {
pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult {
let cmd = args[0];
const SHORT_OPTS: &wstr = L!(":HSabcdefilmnqrstuvwyKPTh");
const SHORT_OPTS: &wstr = L!("HSabcdefilmnqrstuvwyKPTh");
const LONG_OPTS: &[WOption] = &[
wopt(L!("all"), ArgType::NoArgument, 'a'),
@@ -237,6 +237,10 @@ pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
builtin_missing_argument(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);

View File

@@ -140,7 +140,7 @@ pub fn wait(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
let mut print_help = false;
let print_hints = false;
const shortopts: &wstr = L!(":nh");
const shortopts: &wstr = L!("nh");
const longopts: &[WOption] = &[
wopt(L!("any"), ArgType::NoArgument, 'n'),
wopt(L!("help"), ArgType::NoArgument, 'h'),
@@ -159,6 +159,16 @@ pub fn wait(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);

View File

@@ -170,6 +170,7 @@ pub(crate) enum ParsedArgs<'argarray, 'args> {
pub(crate) enum ParseError<'args> {
MissingOptArg,
UnexpectedOptArg(usize),
UnknownColor(&'args wstr),
UnknownUnderlineStyle(&'args wstr),
UnknownOption(usize),
@@ -180,7 +181,7 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
is_builtin: bool,
) -> Result<ParsedArgs<'argarray, 'args>, ParseError<'args>> {
let builtin_extra_args = if is_builtin { 0 } else { "hc".len() };
let short_options = L!(":b:oidru::ch");
let short_options = L!("b:oidru::ch");
let short_options = &short_options[..short_options.len() - builtin_extra_args];
let long_options: &[WOption] = &[
wopt(L!("background"), ArgType::RequiredArgument, 'b'),
@@ -262,6 +263,11 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
return Err(MissingOptArg);
}
}
';' => {
if is_builtin {
return Err(UnexpectedOptArg(w.wopt_index - 1));
}
}
'?' => {
if is_builtin {
return Err(UnknownOption(w.wopt_index - 1));

View File

@@ -59,9 +59,10 @@ enum Ordering {
/// Indicates whether an option takes an argument, and whether that argument
/// is optional.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ArgType {
/// The option takes no arguments.
#[default]
NoArgument,
/// The option takes a required argument.
RequiredArgument,
@@ -107,6 +108,8 @@ enum NextArgv {
FoundOption,
}
use std::borrow::Cow;
pub struct WGetopter<'opts, 'args, 'argarray> {
/// List of arguments. Will not be resized, but can be modified.
pub argv: &'argarray mut [&'args wstr],
@@ -135,10 +138,14 @@ pub struct WGetopter<'opts, 'args, 'argarray> {
/// Used when reordering elements. After scanning is finished, indicates the index
/// after the final non-option skipped during parsing.
pub last_nonopt: usize,
/// Return `:` if an arg is missing.
return_colon: bool,
/// Prevents redundant initialization.
initialized: bool,
// Makes parsing long options more strict (long options must have their full name specified,
// and cannot be given with a single hyphen).
pub strict_long_opts: bool,
/// This will be populated with the elements of the original args that were interpreted
/// as options and arguments to options
pub argv_opts: Vec<Cow<'args, wstr>>,
}
impl<'opts, 'args, 'argarray> WGetopter<'opts, 'args, 'argarray> {
@@ -158,12 +165,20 @@ pub fn new(
ordering: Ordering::Permute,
first_nonopt: 0,
last_nonopt: 0,
return_colon: false,
initialized: false,
strict_long_opts: false,
argv_opts: Vec::new(),
}
}
/// Try to get the next option.
/// Try to get the next option, returning:
/// * None if there are no more options
/// * `Some(`[`NON_OPTION_CHAR`]`)` for a non-option when using [`Ordering::ReturnInOrder`]
/// * `Some('?') for unrecognised options
/// * `Some(':')` for options missing an argument,
/// * `Some(';') for options with an unexpected argument (this is only possible when using the
/// --long=value or -long=value syntax where long was declared as taking no arguments).
/// * Otherwise, `Some(c)`, where `c` is the option's short character
pub fn next_opt(&mut self) -> Option<char> {
assert!(
self.wopt_index <= self.argv.len(),
@@ -227,11 +242,6 @@ fn initialize(&mut self) {
self.ordering = Ordering::Permute;
}
if optstring.char_at(0) == ':' {
self.return_colon = true;
optstring = &optstring[1..];
}
self.shortopts = optstring;
self.initialized = true;
}
@@ -302,14 +312,17 @@ fn next_argv(&mut self) -> NextArgv {
return NextArgv::UnpermutedNonOption;
}
let opt = self.argv[self.wopt_index];
self.argv_opts.push(Cow::Borrowed(opt));
// We've found an option, so we need to skip the initial punctuation.
let skip = if !self.longopts.is_empty() && self.argv[self.wopt_index].char_at(1) == '-' {
let skip = if !self.longopts.is_empty() && opt.char_at(1) == '-' {
2
} else {
1
};
self.remaining_text = self.argv[self.wopt_index][skip..].into();
self.remaining_text = opt[skip..].into();
NextArgv::FoundOption
}
@@ -362,10 +375,12 @@ fn handle_short_opt(&mut self) -> char {
// no following element to consume, then the option
// has no argument.
self.unrecognized_opt = c;
c = if self.return_colon { ':' } else { '?' };
c = ':';
} else {
// Consume the next element.
self.woptarg = Some(self.argv[self.wopt_index]);
let val = self.argv[self.wopt_index];
self.argv_opts.push(Cow::Borrowed(val));
self.woptarg = Some(val);
self.wopt_index += 1;
}
}
@@ -387,17 +402,19 @@ fn update_long_opt(
if self.remaining_text.char_at(name_end) == '=' {
if opt_found.arg_type == ArgType::NoArgument {
self.remaining_text = empty_wstr();
return '?';
return ';';
} else {
self.woptarg = Some(self.remaining_text[(name_end + 1)..].into());
}
} else if opt_found.arg_type == ArgType::RequiredArgument {
if self.wopt_index < self.argv.len() {
self.woptarg = Some(self.argv[self.wopt_index]);
let val = self.argv[self.wopt_index];
self.argv_opts.push(Cow::Borrowed(val));
self.woptarg = Some(val);
self.wopt_index += 1;
} else {
self.remaining_text = empty_wstr();
return if self.return_colon { ':' } else { '?' };
return ':';
}
}
@@ -426,6 +443,9 @@ fn find_matching_long_opt(&self, name_end: usize) -> LongOptMatch<'opts> {
index = i;
exact = true;
break;
} else if self.strict_long_opts {
// It didn't match exactly, continue
continue;
} else if opt.is_none() {
// The option begins with the matching text, but is not
// exactly equal.
@@ -477,6 +497,10 @@ fn handle_long_opt(&mut self, longopt_index: &mut usize) -> Option<char> {
.as_char_slice()
.contains(&self.remaining_text.char_at(0))
{
// Unknown option, assume remaining text is its argument
// (it needs to be saved somewhere for argparse, but we cant save it in
// self.remaining_text as that will break further parsing)
self.woptarg = Some(&self.remaining_text[1..]);
self.remaining_text = empty_wstr();
self.wopt_index += 1;
@@ -515,9 +539,9 @@ fn wgetopt_inner(&mut self, longopt_index: &mut usize) -> Option<char> {
}
// We set things up so that `-f` is parsed as a short-named option if there
// is a valid option to match it to, otherwise we parse it as a long-named
// option. We also make sure that `-fu` is *not* parsed as `-f` with
// an arg `u`.
// is a valid option to match it to, otherwise, if self.strict_long_opts is false,
// we parse it as a long-named option. We also make sure that `-fu` is *not* parsed as
// `-f` with an arg `u`.
if !self.longopts.is_empty() && self.wopt_index < self.argv.len() {
let arg = self.argv[self.wopt_index];
@@ -525,7 +549,8 @@ fn wgetopt_inner(&mut self, longopt_index: &mut usize) -> Option<char> {
// matches options like `--foo`
arg.char_at(0) == '-' && arg.char_at(1) == '-'
// matches options like `-f` if `f` is not a valid shortopt.
|| !self.shortopts.as_char_slice().contains(&arg.char_at(1));
|| (!self.strict_long_opts
&& !self.shortopts.as_char_slice().contains(&arg.char_at(1)));
if try_long {
let retval = self.handle_long_opt(longopt_index);

View File

@@ -31,10 +31,12 @@ begin
# CHECK: 0
set -l
# CHECK: argv hello
# CHECK: argv_opts
end
# Invalid option specs
argparse h-
argparse /
argparse +help
argparse h/help:
argparse h-help::
@@ -44,6 +46,11 @@ argparse h-help=x
#CHECKERR: argparse h-
#CHECKERR: ^
#CHECKERR: (Type 'help argparse' for related documentation)
#CHECKERR: argparse: Short flag '/' invalid, must be alphanum or '#'
#CHECKERR: {{.*}}checks/argparse.fish (line {{\d+}}):
#CHECKERR: argparse /
#CHECKERR: ^
#CHECKERR: (Type 'help argparse' for related documentation)
#CHECKERR: argparse: Short flag '+' invalid, must be alphanum or '#'
#CHECKERR: {{.*}}checks/argparse.fish (line {{\d+}}):
#CHECKERR: argparse +help
@@ -154,6 +161,7 @@ begin
argparse h/help -- help
set -l
# CHECK: argv help
# CHECK: argv_opts
end
# Five args with two matching a flag
@@ -163,12 +171,13 @@ begin
# CHECK: _flag_h '--help' '-h'
# CHECK: _flag_help '--help' '-h'
# CHECK: argv 'help' 'me' 'a lot more'
# CHECK: argv_opts '--help' '-h'
end
# Required, optional, and multiple flags
begin
argparse h/help 'a/abc=' 'd/def=?' 'g/ghk=+' -- help --help me --ghk=g1 --abc=ABC --ghk g2 -d -g g3
set -l
set -lL
# CHECK: _flag_a ABC
# CHECK: _flag_abc ABC
# CHECK: _flag_d
@@ -178,6 +187,7 @@ begin
# CHECK: _flag_h --help
# CHECK: _flag_help --help
# CHECK: argv 'help' 'me'
# CHECK: argv_opts '--help' '--ghk=g1' '--abc=ABC' '--ghk' 'g2' '-d' '-g' 'g3'
end
# --stop-nonopt works
@@ -189,6 +199,7 @@ begin
# CHECK: _flag_h -h
# CHECK: _flag_help -h
# CHECK: argv 'non-opt' 'second non-opt' '--help'
# CHECK: argv_opts '-a' 'A1' '-h' '--abc' 'A2'
end
# Implicit int flags work
@@ -197,6 +208,7 @@ begin
set -l
# CHECK: _flag_val 123
# CHECK: argv 'abc' 'def'
# CHECK: argv_opts -123
end
begin
argparse v/verbose '#-val' 't/token=' -- -123 a1 --token woohoo --234 -v a2 --verbose
@@ -207,6 +219,7 @@ begin
# CHECK: _flag_val -234
# CHECK: _flag_verbose '-v' '--verbose'
# CHECK: argv 'a1' 'a2'
# CHECK: argv_opts '-123' '--token' 'woohoo' '--234' '-v' '--verbose'
end
# Should be set to 987
@@ -216,6 +229,7 @@ begin
# CHECK: _flag_m 987
# CHECK: _flag_max 987
# CHECK: argv 'argle' 'bargle'
# CHECK: argv_opts -987
end
# Should be set to 765
@@ -225,6 +239,7 @@ begin
# CHECK: _flag_m 765
# CHECK: _flag_max 765
# CHECK: argv 'argle' 'bargle'
# CHECK: argv_opts '-987' '--max' '765'
end
# Bool short flag only
@@ -234,6 +249,7 @@ begin
# CHECK: _flag_C -C
# CHECK: _flag_v '-v' '-v'
# CHECK: argv 'arg1' 'arg2'
# CHECK: argv_opts '-C' '-v' '-v'
end
# Value taking short flag only
@@ -244,6 +260,7 @@ begin
# CHECK: _flag_verbose '--verbose' '-v'
# CHECK: _flag_x arg2
# CHECK: argv arg1
# CHECK: argv_opts '--verbose' '-v' '-x' 'arg2'
end
# Implicit int short flag only
@@ -254,6 +271,7 @@ begin
# CHECK: _flag_verbose '-v' '-v' '-v'
# CHECK: _flag_x 321
# CHECK: argv 'argle' 'bargle'
# CHECK: argv_opts '-v' '-v' '-v' '-x' '321'
end
# Implicit int short flag only with custom validation passes
@@ -264,6 +282,7 @@ begin
# CHECK: _flag_verbose '-v' '-v' '-v'
# CHECK: _flag_x 499
# CHECK: argv
# CHECK: argv_opts '-v' '-v' '-x' '499' '-v'
end
# Implicit int short flag only with custom validation fails
@@ -284,28 +303,32 @@ and echo unexpected argparse return status >&2
# CHECKERR: argparse: Value '765x' for flag 'max' is not an integer
# CHECKERR: argparse: Value 'a1' for flag 'm' is not an integer
begin
# Check the exit status from argparse validation
argparse 'm#max!set -l | grep "^_flag_"; function x; return 57; end; x' -- argle --max=83 bargle 2>&1
set -l saved_status $status
test $saved_status -eq 57
and echo expected argparse return status $saved_status
# CHECK: _flag_name max
# CHECK: _flag_value 83
# CHECK: expected argparse return status 57
argparse 'm#max!set -l | grep "^_flag_"; function x; return 57; end; x' -- argle --max=83 bargle 2>&1
set -l saved_status $status
test $saved_status -eq 57
and echo expected argparse return status $saved_status
# CHECK: _flag_name max
# CHECK: _flag_value 83
# CHECK: expected argparse return status 57
end
# Explicit int flag validation
# These should fail
argparse 'm#max!_validate_int --min 1 --max 1' -- argle -m2 bargle
and echo unexpected argparse return status $status >&2
argparse 'm#max!_validate_int --min 0 --max 1' -- argle --max=-1 bargle
and echo unexpected argparse return status $status >&2
# CHECKERR: argparse: Value '2' for flag 'm' greater than max allowed of '1'
# CHECKERR: argparse: Value '-1' for flag 'max' less than min allowed of '0'
# These should succeed
argparse 'm/max=!_validate_int --min 0 --max 1' -- argle --max=0 bargle
or echo unexpected argparse return status $status >&2
argparse 'm/max=!_validate_int --min 0 --max 1' -- argle --max=1 bargle
or echo unexpected argparse return status $status >&2
begin
# Explicit int flag validation
# These should fail
argparse 'm#max!_validate_int --min 1 --max 1' -- argle -m2 bargle
and echo unexpected argparse return status $status >&2
argparse 'm#max!_validate_int --min 0 --max 1' -- argle --max=-1 bargle
and echo unexpected argparse return status $status >&2
# CHECKERR: argparse: Value '2' for flag 'm' greater than max allowed of '1'
# CHECKERR: argparse: Value '-1' for flag 'max' less than min allowed of '0'
# These should succeed
argparse 'm/max=!_validate_int --min 0 --max 1' -- argle --max=0 bargle
or echo unexpected argparse return status $status >&2
argparse 'm/max=!_validate_int --min 0 --max 1' -- argle --max=1 bargle
or echo unexpected argparse return status $status >&2
end
# Errors use function name by default
function notargparse
@@ -316,17 +339,138 @@ notargparse
true
# Ignoring unknown options
argparse -i a=+ b=+ -- -a alpha -b bravo -t tango -a aaaa --wurst
or echo unexpected argparse return status $status >&2
# The unknown options are removed _entirely_.
echo $argv
echo $_flag_a
# CHECK: -t tango --wurst
# CHECK: alpha aaaa
begin
# Ignoring unknown options
argparse -i a=+ b=+ -- -a alpha -b bravo -t tango -a aaaa --wurst
or echo unexpected argparse return status $status >&2
# The unknown options are removed _entirely_.
echo $argv
echo $argv_opts
echo $_flag_a
# CHECK: -t tango --wurst
# CHECK: -a alpha -b bravo -a aaaa
# CHECK: alpha aaaa
end
# Check for crash when last option is unknown
argparse -i b/break -- "-b kubectl get pods -l name=foo"
begin
# Ignoring unknown long options that share a character with a known short options
argparse -i o -- --long=value --long
or echo unexpected argparse return status $status >&2
set -l
# CHECK: argv '--long=value' '--long'
# CHECK: argv_opts
end
begin
# Check for crash when last option is unknown
# Note that because of the quotation marks, there is a single argument
# starting with the known short option '-b', and continuing with an unknown short option
# that starts with a space
argparse -i b/break -- "-b kubectl get pods -l name=foo"
set -l
# CHECK: _flag_b -b
# CHECK: _flag_break -b
# CHECK: argv '- kubectl get pods -l name=foo'
# CHECK: argv_opts -b
end
begin
# Ignore unknown and move unknown are mutually exclusive
argparse -i --move-unknown --
#CHECKERR: argparse: --ignore-unknown --move-unknown: options cannot be used together
#CHECKERR: {{.*}}checks/argparse.fish (line {{\d+}}):
#CHECKERR: argparse -i --move-unknown --
#CHECKERR: ^
#CHECKERR: (Type 'help argparse' for related documentation)
end
begin
# Moving unknown options
argparse --move-unknown --unknown-arguments=optional h i -- -hoa -oia
echo -- $argv
#CHECK:
echo -- $argv_opts
#CHECK: -hoa -oia
echo $_flag_h
#CHECK: -h
set -q _flag_i
or echo No flag I
#CHECK: No flag I
end
begin
# Moving unknown options
argparse -u a=+ b=+ -- -a alpha -b bravo -t tango -a aaaa --wurst
or echo unexpected argparse return status $status >&2
# The unknown options are removed _entirely_.
echo $argv
echo $argv_opts
echo $_flag_a
# CHECK: tango
# CHECK: -a alpha -b bravo -t -a aaaa --wurst
# CHECK: alpha aaaa
end
begin
# Move unknown long options that share a character with a known short options
argparse -u o -- --long=value --long
or echo unexpected argparse return status $status >&2
set -l
# CHECK: argv
# CHECK: argv_opts '--long=value' '--long'
end
begin
argparse -u b/break -- "-b kubectl get pods -l name=foo"
set -l
# CHECK: _flag_b -b
# CHECK: _flag_break -b
# CHECK: argv
# CHECK: argv_opts '-b kubectl get pods -l name=foo'
end
begin
# Checking unknown-arguments, with errors
argparse -i -U none -- --long=value
# CHECKERR: argparse: --long=value: option does not take an argument
argparse -i -U required -- -u
# CHECKERR: argparse: -u: option requires an argument
argparse -i -U required -- --long
# CHECKERR: argparse: --long: option requires an argument
end
begin
argparse -U none -i b= -- -abv=val in --long between -u
set -l
# CHECK: _flag_b v=val
# CHECK: argv '-a' 'in' '--long' 'between' '-u'
# CHECK: argv_opts -bv=val
end
begin
argparse -U none b= -- -abv in --long between -u
set -l
# CHECK: _flag_b v
# CHECK: argv 'in' 'between'
# CHECK: argv_opts '-abv' '--long' '-u'
end
begin
argparse -iU required b= -- -abv -b -b --long -b -u -b
set -l
# CHECK: _flag_b -b
# CHECK: argv '-abv' '--long' '-b' '-u' '-b'
# CHECK: argv_opts '-b' '-b'
end
begin
argparse -uU required b= -- -abv -b -b --long -b -u -b
set -l
# CHECK: _flag_b -b
# CHECK: argv
# CHECK: argv_opts '-abv' '-b' '-b' '--long' '-b' '-u' '-b'
end
begin
# Checking arguments after "--"
@@ -334,70 +478,62 @@ begin
printf '%s\n' $argv
# CHECK: a
# CHECK: b
printf '%s\n' $argv_opts
# CHECK: -a
# CHECK: --alpha
end
# #5864 - short flag only with same validation function.
# Checking validation for short flags only
argparse 'i=!_validate_int' 'o=!_validate_int' -- -i 2 -o banana
argparse 'i=!_validate_int' 'o=!_validate_int' -- -i -o banana
# CHECKERR: argparse: Value 'banana' for flag 'o' is not an integer
# CHECKERR: argparse: Value '-o' for flag 'i' is not an integer
begin
# #5864 - short flag only with same validation function.
# Checking validation for short flags only
argparse 'i=!_validate_int' 'o=!_validate_int' -- -i 2 -o banana
argparse 'i=!_validate_int' 'o=!_validate_int' -- -i -o banana
# CHECKERR: argparse: Value 'banana' for flag 'o' is not an integer
# CHECKERR: argparse: Value '-o' for flag 'i' is not an integer
argparse 'i=!_validate_int' 'o=!_validate_int' -- -i 2 -o 3
set -l
# CHECK: _flag_a 'alpha' 'aaaa'
# CHECK: _flag_b -b
# CHECK: _flag_break -b
# CHECK: _flag_i 2
# CHECK: _flag_m 1
# CHECK: _flag_max 1
# CHECK: _flag_o 3
# CHECK: argv
# CHECK: saved_status 57
# CHECK: argv_opts '-i' '2' '-o' '3'
end
# long-only flags
begin
argparse installed= foo -- --installed=no --foo
set -l
# CHECK: _flag_a 'alpha' 'aaaa'
# CHECK: _flag_b -b
# CHECK: _flag_break -b
# CHECK: _flag_foo --foo
# CHECK: _flag_installed no
# CHECK: _flag_m 1
# CHECK: _flag_max 1
# CHECK: argv
# CHECK: saved_status 57
# CHECK: argv_opts '--installed=no' '--foo'
end
# long-only flags with one letter
begin
argparse i-i= /f -- --i=no --f
set -l
# CHECK: _flag_f --f
# CHECK: _flag_i no
# CHECK: argv
# CHECK: argv_opts '--i=no' '--f'
end
begin
argparse installed='!_validate_int --max 12' foo -- --installed=5 --foo
set -l
# CHECK: _flag_a 'alpha' 'aaaa'
# CHECK: _flag_b -b
# CHECK: _flag_break -b
# CHECK: _flag_foo --foo
# CHECK: _flag_installed 5
# CHECK: _flag_m 1
# CHECK: _flag_max 1
# CHECK: argv
# CHECK: saved_status 57
# CHECK: argv_opts '--installed=5' '--foo'
end
begin
argparse '#num' installed= -- --installed=5 -5
set -l
# CHECK: _flag_a 'alpha' 'aaaa'
# CHECK: _flag_b -b
# CHECK: _flag_break -b
# CHECK: _flag_installed 5
# CHECK: _flag_m 1
# CHECK: _flag_max 1
# CHECK: _flag_num 5
# CHECK: argv
# CHECK: saved_status 57
# CHECK: argv_opts '--installed=5' '-5'
end
begin
@@ -416,15 +552,16 @@ argparse r/required= -- foo --required
# No args is an error
fish_opt
and echo unexpected status $status
#CHECKERR: fish_opt: The --short flag is required and must be a single character
#CHECKERR: fish_opt: Either the --short or --long flag must be provided
# No short flag or an invalid short flag is an error
fish_opt -l help
# Long-only with no long makes no sense
fish_opt -s h --long-only
and echo unexpected status $status
#CHECKERR: fish_opt: The --short flag is required and must be a single character
#CHECKERR: fish_opt: The --long-only flag requires the --long flag
fish_opt -s help
and echo unexpected status $status
#CHECKERR: fish_opt: The --short flag is required and must be a single character
#CHECKERR: fish_opt: The --short flag must be a single character
# A required and optional arg makes no sense
fish_opt -s h -l help -r --optional-val
@@ -432,15 +569,20 @@ and echo unexpected status $status
#CHECKERR: fish_opt: o/optional-val r/required-val: options cannot be used together
# XXX FIXME the error should output -r and --optional-val: what the user used
# A repeated and optional arg makes no sense
fish_opt -s h -l help --multiple-vals --optional-val
and echo unexpected status $status
#CHECKERR: fish_opt: multiple-vals o/optional-val: options cannot be used together
# An unexpected arg not associated with a flag is an error
fish_opt -s h -l help hello
and echo unexpected status $status
#CHECKERR: fish_opt: expected <= 0 arguments; got 1
#CHECKERR: fish_opt: Extra non-option arguments were provided
# A -v / --validate without any arguments is an error
fish_opt -s h -l help -rv
and echo unexpected status $status
#CHECKERR: fish_opt: The --validate flag requires subsequent arguments
# A -v / --validate for boolean options is an error
fish_opt -s h -l help -v echo hello
and echo unexpected status $status
#CHECKERR: fish_opt: The --validate flag requires the --required-val, --optional-value, or --multiple-vals flag
# Now verify that valid combinations of options produces the correct output.
@@ -449,6 +591,16 @@ fish_opt -s h
or echo unexpected status $status
#CHECK: h
# Long flag only
fish_opt -l help
or echo unexpected status $status
#CHECK: /help
# One character long flag
fish_opt -l h
or echo unexpected status $status
#CHECK: /h
# Bool, short and long
fish_opt --short h --long help
or echo unexpected status $status
@@ -458,38 +610,50 @@ or echo unexpected status $status
fish_opt --short h --long help --long-only
#CHECK: h-help
# Required val, short and long but the short var cannot be used
fish_opt --short h --long help -r --long-only
# Required val and long
fish_opt --long help -r
or echo unexpected status $status
#CHECK: h-help=
#CHECK: /help=
# Optional val, short and long valid
fish_opt --short h -l help --optional-val
# Optional val, short and long valid, and delete
fish_opt --short h -l help --optional-val --delete
or echo unexpected status $status
#CHECK: h/help=?
#CHECK: h/help=?&
# Optional val, short and long but the short var cannot be used
fish_opt --short h -l help --optional-val --long-only
or echo unexpected status $status
#CHECK: h-help=?
# Repeated val, short and long valid
fish_opt --short h -l help --multiple-vals
# Repeated val, short and long valid, with validate
fish_opt --short h -l help --multiple-vals --validate _validate_int --max 500
or echo unexpected status $status
#CHECK: h/help=+
#CHECK: h/help=+!_validate_int --max 500
# Repeated val, short and long but short not valid
fish_opt --short h -l help --multiple-vals --long-only
# Repeated and optional val, short and long valid
fish_opt --short h -l help --optional-val -m
or echo unexpected status $status
#CHECK: h-help=+
#CHECK: h/help=*
# Repeated val, short only
fish_opt -s h --multiple-vals
# Repeated val and short, with validate
fish_opt -ml help --long-only -v test \$_flag_value != "' '"
or echo unexpected status $status
fish_opt -s h --multiple-vals --long-only
#CHECK: /help=+!test $_flag_value != ' '
# Repeated and optional val, short and long but short not valid
fish_opt --short h -l help --long-only -mo
or echo unexpected status $status
#CHECK: h=+
#CHECK: h=+
#CHECK: h-help=*
# Repeated val, short only, and deleted
fish_opt -ms h -md
or echo unexpected status $status
#CHECK: h=+&
# Repeated and optional val, short only
fish_opt -s h -om
or echo unexpected status $status
#CHECK: h=*
function wrongargparse
argparse -foo -- banana
@@ -506,13 +670,130 @@ begin
#CHECKERR: (Type 'help argparse' for related documentation)
end
begin
argparse -U
#CHECKERR: argparse: -U: option requires an argument
#CHECKERR: {{.*}}checks/argparse.fish (line {{\d+}}):
#CHECKERR: argparse -U
#CHECKERR: ^
#CHECKERR: (Type 'help argparse' for related documentation)
end
begin
argparse --unknown-arguments what --
#CHECKERR: argparse: Invalid --unknown-arguments value 'what'
#CHECKERR: {{.*}}checks/argparse.fish (line {{\d+}}):
#CHECKERR: argparse --unknown-arguments what --
#CHECKERR: ^
#CHECKERR: (Type 'help argparse' for related documentation)
end
begin
argparse f!echo hello -- -f
#CHECKERR: argparse: Invalid option spec 'f!echo' at char '!'
end
begin
argparse --ignore-unknown h i -- -hoa -oia
echo -- $argv
#CHECK: -hoa -oia
#CHECK: -oa -oia
echo -- $argv_opts
#CHECK: -h
echo $_flag_h
#CHECK: -h
set -q _flag_i
or echo No flag I
#CHECK: No flag I
end
# Tests for --delete
begin
argparse 'a/long&' -- before -a inbetween --long after
set -l
# CHECK: _flag_a '-a' '--long'
# CHECK: _flag_long '-a' '--long'
# CHECK: argv 'before' 'inbetween' 'after'
# CHECK: argv_opts
end
begin
argparse 'a/long=+&' '#int&' -- -123 before -a -a inbetween -astuck ater -long=long
set -l
# CHECK: _flag_a '-a' 'stuck' 'long'
# CHECK: _flag_int 123
# CHECK: _flag_long '-a' 'stuck' 'long'
# CHECK: argv 'before' 'inbetween' 'ater'
# CHECK: argv_opts
end
begin
argparse 'd=?&' a b -- -d -d3 -ad -bd345
set -l
# CHECK: _flag_a -a
# CHECK: _flag_b -b
# CHECK: _flag_d 345
# CHECK: argv
# CHECK: argv_opts '-a' '-b'
end
begin
argparse 'd&' a b 'v=' -- 0 -adbv124 1 -abdv125 2 -dabv124 3 -vd3
set -l
# CHECK: _flag_a '-a' '-a' '-a'
# CHECK: _flag_b '-b' '-b' '-b'
# CHECK: _flag_d '-d' '-d' '-d'
# CHECK: _flag_v d3
# CHECK: argv '0' '1' '2' '3'
# CHECK: argv_opts '-abv124' '-abv125' '-abv124' '-vd3'
end
argparse a/alpha -- --banna=value
# CHECKERR: argparse: --banna=value: unknown option
# But this gives a better message
argparse a/alpha -- --alpha=value --banna=value
# CHECKERR: argparse: --alpha=value: option does not take an argument
# Check behaviour without -S/--strict-longopts option
begin
argparse long valu=+ -- --lon -long -lon -valu=3 -valu 4 -val=3 -val 4
set -lL
# CHECK: _flag_long '--long' '--long' '--long'
# CHECK: _flag_valu '3' '4' '3' '4'
# CHECK: argv
# CHECK: argv_opts '--lon' '-long' '-lon' '-valu=3' '-valu' '4' '-val=3' '-val' '4'
argparse amb ambig -- -am
# CHECKERR: argparse: -am: unknown option
argparse a ambig -- -ambig
# CHECKERR: argparse: -ambig: unknown option
argparse long -- -long3
# CHECKERR: argparse: -long3: unknown option
end
# Check behaviour with -S/--strict-longopts option
begin
argparse -S long -- --lon
# CHECKERR: argparse: --lon: unknown option
argparse -S long -- -long
# CHECKERR: argparse: -long: unknown option
argparse --strict-longopts long -- -lon
# CHECKERR: argparse: -lon: unknown option
argparse -S value=+ -- -valu=3
# CHECKERR: argparse: -valu=3: unknown option
argparse --strict-longopts value=+ -- -valu 4
# CHECKERR: argparse: -valu: unknown option
end
# Check =* options
begin
set -l _flag_o previous value
argparse 'o/opt=*' -- -o non-value -oval --opt arg --opt=456
set -l
# CHECK: _flag_o '' 'val' '' '456'
# CHECK: _flag_opt '' 'val' '' '456'
# CHECK: argv 'non-value' 'arg'
# CHECK: argv_opts '-o' '-oval' '--opt' '--opt=456'
end
# Check that the argparse's are properly wrapped in begin blocks
set -l

View File

@@ -1,2 +1,2 @@
#RUN: %fish -Z
# CHECKERR: {{.*fish}}: {{unrecognized option: Z|invalid option -- '?Z'?|unknown option -- Z|illegal option -- Z|-Z: unknown option}}
#RUN: %fish --interactive=value
# CHECKERR: {{.*fish}}: --interactive=value: option does not take an argument

View File

@@ -229,3 +229,8 @@ functions --banana
# CHECKERR: functions: --banana: unknown option
echo $status
# CHECK: 2
functions --all=arg
# CHECKERR: functions: --all=arg: option does not take an argument
echo $status
# CHECK: 2

View File

@@ -16,9 +16,12 @@ emit linenumber
type --nonexistent-option-so-we-get-a-backtrace
# CHECKERR: type: --nonexistent-option-so-we-get-a-backtrace: unknown option
type --short=cd
# CHECKERR: type: --short=cd: option does not take an argument
function line-number
status line-number
end
line-number
# CHECK: 20
# CHECK: 23

View File

@@ -13,6 +13,15 @@ string escape (set_color red reset yellow)
string escape (set_color --background=reset)
# CHECKERR: set_color: Unknown color 'reset'
string escape (set_color --bold=red)
# CHECKERR: set_color: --bold=red: option does not take an argument
#CHECKERR: {{.*}}checks/set_color.fish (line {{\d+}}):
#CHECKERR: set_color --bold=red
#CHECKERR: ^
#CHECKERR: in command substitution
#CHECKERR: called on line {{\d+}} of file {{.*}}checks/set_color.fish
#CHECKERR: (Type 'help set_color' for related documentation)
string escape (set_color --bold red --background=normal)
# CHECK: \e\[31m\e\[49m\e\[1m
string escape (set_color --bold red --background=blue)