From 663430a925c6db768fbd93596cd72b6f7f13d0b2 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Sun, 3 Aug 2025 10:07:08 +1000 Subject: [PATCH] Added support to fish_opt for defining a long flag with no short flag. Specifically, this now makes the -s/--short option to fish_opt optional when the -l/--long option is given. This commit does not modify argparse, as it already supports defining long flags without a corresponding short flag, however fish_opt would never take advantage of this feature. Note that due to a limitation in argparse, fish_opt will give an error if you try to define a one-character --long flag without also providing a --short option. For backwards compatibility, the --long-only flag is still included with fish_opt, and when used with -s/--short, will behave as before (the short flag is still defined, but argparse will fail if it is actually used by the parsed arguments, moreover the _flag_ option variables will not be defined). This can however be used to define a one character long flag. --- CHANGELOG.rst | 1 + doc_src/cmds/argparse.rst | 6 ++-- doc_src/cmds/fish_opt.rst | 12 ++++---- po/de.po | 11 +++++++- po/en.po | 11 +++++++- po/fr.po | 13 +++++++-- po/pl.po | 11 +++++++- po/pt_BR.po | 11 +++++++- po/sv.po | 11 +++++++- po/zh_CN.po | 13 +++++++-- share/functions/fish_opt.fish | 35 +++++++++++++---------- tests/checks/argparse.fish | 52 +++++++++++++++++++++++------------ 12 files changed, 138 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d892111c0..045c50b36 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -49,6 +49,7 @@ Scripting improvements - ``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. Interactive improvements ------------------------ diff --git a/doc_src/cmds/argparse.rst b/doc_src/cmds/argparse.rst index d923e37af..e879a462c 100644 --- a/doc_src/cmds/argparse.rst +++ b/doc_src/cmds/argparse.rst @@ -136,9 +136,11 @@ 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, seperated from the short flag (if present) by a ``/``. If neither a short flag nor long flag are present, an error is reported. + + - 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 diff --git a/doc_src/cmds/fish_opt.rst b/doc_src/cmds/fish_opt.rst index 97d11965c..23aeb5467 100644 --- a/doc_src/cmds/fish_opt.rst +++ b/doc_src/cmds/fish_opt.rst @@ -8,7 +8,7 @@ Synopsis .. synopsis:: - fish_opt -s ALPHANUM [-l LONG-NAME] [-ormd] [--long-only] + fish_opt [-s ALPHANUM] [-l LONG-NAME] [-ormd] [--long-only] fish_opt --help Description @@ -19,13 +19,13 @@ This command provides a way to produce option specifications suitable for use wi The following ``argparse`` options are available: **-s** or **--short** *ALPHANUM* - Takes a single letter that is used as the short flag in the option being defined. This option is mandatory. + 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** *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. + 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. If there is no **--short** flag, the long flag name must be more than one character (use **--short** together with **--long-only** to bypass this restriction). **--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. + 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. @@ -67,7 +67,7 @@ Same as above but with a second flag that requires a value: 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 the long flag name (``--token``) can be used: +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: @@ -75,6 +75,6 @@ Same as above but with a third flag that can be given multiple times saving the 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 --long=token --multiple-vals) argparse $options -- $argv diff --git a/po/de.po b/po/de.po index 9170834b3..18d0f0e15 100644 --- a/po/de.po +++ b/po/de.po @@ -1930,6 +1930,9 @@ 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 "" @@ -1942,7 +1945,13 @@ 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 flag must be more than one character when no --short flag is provided\\n" +msgstr "" + +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 number of positions to skip must be a non-negative integer\\n" diff --git a/po/en.po b/po/en.po index a4c1f0b42..22a008e4b 100644 --- a/po/en.po +++ b/po/en.po @@ -1926,6 +1926,9 @@ 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" @@ -1938,7 +1941,13 @@ 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 flag must be more than one character when no --short flag is provided\\n" +msgstr "" + +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 number of positions to skip must be a non-negative integer\\n" diff --git a/po/fr.po b/po/fr.po index cb66cfecc..7c06ec93b 100644 --- a/po/fr.po +++ b/po/fr.po @@ -2027,6 +2027,9 @@ 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" @@ -2039,8 +2042,14 @@ 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 : L’option --short est requise et doit être un caractère unique\\n" +msgid "%s: The --long flag must be more than one character when no --short flag is provided\\n" +msgstr "" + +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 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" diff --git a/po/pl.po b/po/pl.po index 9b50bbe2b..c22c2f035 100644 --- a/po/pl.po +++ b/po/pl.po @@ -1922,6 +1922,9 @@ 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 "" @@ -1934,7 +1937,13 @@ 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 flag must be more than one character when no --short flag is provided\\n" +msgstr "" + +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 number of positions to skip must be a non-negative integer\\n" diff --git a/po/pt_BR.po b/po/pt_BR.po index 71834756f..bd73fc1dc 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -1927,6 +1927,9 @@ 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 "" @@ -1939,7 +1942,13 @@ 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 flag must be more than one character when no --short flag is provided\\n" +msgstr "" + +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 number of positions to skip must be a non-negative integer\\n" diff --git a/po/sv.po b/po/sv.po index 8d108af6c..fee985a49 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1923,6 +1923,9 @@ 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" @@ -1935,7 +1938,13 @@ 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 flag must be more than one character when no --short flag is provided\\n" +msgstr "" + +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 number of positions to skip must be a non-negative integer\\n" diff --git a/po/zh_CN.po b/po/zh_CN.po index 394b1bc3c..362c27d71 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -1925,6 +1925,9 @@ 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" @@ -1937,8 +1940,14 @@ 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 flag must be more than one character when no --short flag is provided\\n" +msgstr "" + +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 number of positions to skip must be a non-negative integer\\n" msgstr "%s:要跳过的位置数必须是非负整数\\n" diff --git a/share/functions/fish_opt.fish b/share/functions/fish_opt.fish index 21e99730a..b4f71ab49 100644 --- a/share/functions/fish_opt.fish +++ b/share/functions/fish_opt.fish @@ -1,8 +1,17 @@ # 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 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 + else if not set -q _flag_short && test 1 -eq (string length -- $_flag_long) + printf (_ "%s: The --long flag must be more than one character when no --short flag is provided\n") fish_opt >&2 return 1 end @@ -11,8 +20,7 @@ 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=' d/delete o/optional-val r/required-val m/multiple-vals - set options $options L-long-only + set -l options h/help 's/short=' 'l/long=' d/delete o/optional-val r/required-val m/multiple-vals long-only argparse -n fish_opt --max-args=0 --exclusive=r,o $options -- $argv or return @@ -24,15 +32,14 @@ function fish_opt -d 'Produce an option specification suitable for use with `arg __fish_opt_validate_args 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 && set -q _flag_optional_val diff --git a/tests/checks/argparse.fish b/tests/checks/argparse.fish index 357a0cfad..82e1da86e 100644 --- a/tests/checks/argparse.fish +++ b/tests/checks/argparse.fish @@ -502,6 +502,16 @@ begin # CHECK: argv_opts '--installed=no' '--foo' end +# long-only flags with one letter +begin + argparse i-i= a-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 @@ -536,15 +546,22 @@ 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 + +# One character long flag with no short isn't supported +fish_opt -l h +and echo unexpected status $status +#CHECKERR: fish_opt: The --long flag must be more than one character when no --short flag is provided + + 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 @@ -564,6 +581,11 @@ 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 + # Bool, short and long fish_opt --short h --long help or echo unexpected status $status @@ -573,10 +595,10 @@ 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, and delete fish_opt --short h -l help --optional-val --delete @@ -598,10 +620,10 @@ fish_opt --short h -l help --optional-val -m or echo unexpected status $status #CHECK: h/help=* -# Repeated val, short and long but short not valid -fish_opt --short h -ml help --long-only +# Repeated val and short +fish_opt -ml help --long-only or echo unexpected status $status -#CHECK: h-help=+ +#CHECK: help=+ # Repeated and optional val, short and long but short not valid fish_opt --short h -l help --long-only -mo @@ -609,19 +631,13 @@ or echo unexpected status $status #CHECK: h-help=* # Repeated val, short only, and deleted -fish_opt -s h --multiple-vals -or echo unexpected status $status -#CHECK: h=+ -fish_opt -s h -md --long-only +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 -fish_opt -s h --optional-val --multiple-vals --long-only -or echo unexpected status $status -#CHECK: h=* #CHECK: h=* function wrongargparse