From c403822face3b561ac488d988ba592dce27a44f9 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Sun, 3 Aug 2025 10:07:08 +1000 Subject: [PATCH] Modified argparse to support one character long only options. This fixes an issue noticed in the previous commit (the made the -s/--short option optional to fish_opt): it was impossible to define a single character long flag, unless you also provided a single-character short flag equivalent. This commit works by allowing an option spec to start with a '/', treating the subsequent alpha-numeric characters as a long flag name. In detail, consider the following: - s defines a -s short flag - ss defines an --ss long flag - /ss (new) also defines a --ss long flag - s/s defines a -s short flag and an --s long flag - s-s defines a --s long flag (if there's already an -s short flag, you'd have to change the first s, e.g. S-s) - /s (new) defines a --s long flag - s/ is an error (a long flag name must follow the /) Note that without using --strict-longopts, a long flag --s can always be abbreviated as -s, provided that -s isn't defined as a separate short flag. This 'issue' fixed by this commit is relatively trivial, however it does allow simplifying the documentation for fish_opt (since it no longer needs to mention the restriction). In particular, this commit makes the --long-only flag to fish_opt completely unnecessary (but it is kept for backwards compatibility). --- CHANGELOG.rst | 1 + doc_src/cmds/argparse.rst | 6 +++++- doc_src/cmds/fish_opt.rst | 4 ++-- po/de.po | 3 --- po/en.po | 3 --- po/fr.po | 3 --- po/pl.po | 3 --- po/pt_BR.po | 3 --- po/sv.po | 3 --- po/zh_CN.po | 3 --- share/functions/fish_opt.fish | 5 +---- src/builtins/argparse.rs | 9 ++++++--- tests/checks/argparse.fish | 25 +++++++++++++++---------- 13 files changed, 30 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 045c50b36..ecb12c044 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -50,6 +50,7 @@ Scripting improvements - ``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. Interactive improvements ------------------------ diff --git a/doc_src/cmds/argparse.rst b/doc_src/cmds/argparse.rst index e879a462c..cd5f052aa 100644 --- a/doc_src/cmds/argparse.rst +++ b/doc_src/cmds/argparse.rst @@ -138,7 +138,9 @@ Each option specification consists of: - An optional alphanumeric short flag character. -- 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. +- 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). @@ -255,6 +257,8 @@ Some *OPTION_SPEC* examples: - ``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. diff --git a/doc_src/cmds/fish_opt.rst b/doc_src/cmds/fish_opt.rst index 23aeb5467..b03f1b127 100644 --- a/doc_src/cmds/fish_opt.rst +++ b/doc_src/cmds/fish_opt.rst @@ -22,10 +22,10 @@ The following ``argparse`` options are available: 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. 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). + 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, even if the short flag is defined (i.e., **--short** is specified). + 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. diff --git a/po/de.po b/po/de.po index 18d0f0e15..9321cece5 100644 --- a/po/de.po +++ b/po/de.po @@ -1945,9 +1945,6 @@ msgstr "%s: Ungültige Maske '%s'\\n" msgid "%s: Not inside of command substitution" msgstr "" -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 "" diff --git a/po/en.po b/po/en.po index 22a008e4b..7dcf84e2a 100644 --- a/po/en.po +++ b/po/en.po @@ -1941,9 +1941,6 @@ msgstr "%s: Invalid mask “%s”\\n" msgid "%s: Not inside of command substitution" msgstr "%s: Not inside of command substitution" -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 "" diff --git a/po/fr.po b/po/fr.po index 7c06ec93b..9240de8c1 100644 --- a/po/fr.po +++ b/po/fr.po @@ -2042,9 +2042,6 @@ msgstr "%s : Masque '%s' invalide\\n" msgid "%s: Not inside of command substitution" msgstr "%s : hors de la substitution de commande" -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 "" diff --git a/po/pl.po b/po/pl.po index c22c2f035..ca3b00cd6 100644 --- a/po/pl.po +++ b/po/pl.po @@ -1937,9 +1937,6 @@ msgstr "" msgid "%s: Not inside of command substitution" msgstr "" -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 "" diff --git a/po/pt_BR.po b/po/pt_BR.po index bd73fc1dc..6c67fe1a3 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -1942,9 +1942,6 @@ msgstr "" msgid "%s: Not inside of command substitution" msgstr "" -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 "" diff --git a/po/sv.po b/po/sv.po index fee985a49..2000963ab 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1938,9 +1938,6 @@ msgstr "%s: Ogiltigt mask '%s'\\n" msgid "%s: Not inside of command substitution" msgstr "" -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 "" diff --git a/po/zh_CN.po b/po/zh_CN.po index 362c27d71..92408da2c 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -1940,9 +1940,6 @@ msgstr "%s:无效的掩码'%s'\\n" msgid "%s: Not inside of command substitution" msgstr "%s: 命令替换不在内" -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 "" diff --git a/share/functions/fish_opt.fish b/share/functions/fish_opt.fish index b4f71ab49..ac793a3a0 100644 --- a/share/functions/fish_opt.fish +++ b/share/functions/fish_opt.fish @@ -10,9 +10,6 @@ function __fish_opt_validate_args --no-scope-shadowing 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 return 0 @@ -33,7 +30,7 @@ function fish_opt -d 'Produce an option specification suitable for use with `arg or return if not set -q _flag_short - set opt_spec $_flag_long + set opt_spec "/$_flag_long" else if not set -q _flag_long set opt_spec $_flag_short else if set -q _flag_long_only diff --git a/src/builtins/argparse.rs b/src/builtins/argparse.rs index 796812449..c84cba249 100644 --- a/src/builtins/argparse.rs +++ b/src/builtins/argparse.rs @@ -364,10 +364,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; } @@ -392,7 +394,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, diff --git a/tests/checks/argparse.fish b/tests/checks/argparse.fish index 82e1da86e..f471c2ee4 100644 --- a/tests/checks/argparse.fish +++ b/tests/checks/argparse.fish @@ -36,6 +36,7 @@ end # Invalid option specs argparse h- +argparse / argparse +help argparse h/help: argparse h-help:: @@ -45,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 @@ -504,7 +510,7 @@ end # long-only flags with one letter begin - argparse i-i= a-f -- --i=no --f + argparse i-i= /f -- --i=no --f set -l # CHECK: _flag_f --f # CHECK: _flag_i no @@ -553,12 +559,6 @@ fish_opt -s h --long-only and echo unexpected status $status #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 must be a single character @@ -584,7 +584,12 @@ or echo unexpected status $status # Long flag only fish_opt -l help or echo unexpected status $status -#CHECK: help +#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 @@ -598,7 +603,7 @@ fish_opt --short h --long help --long-only # Required val and long fish_opt --long help -r or echo unexpected status $status -#CHECK: help= +#CHECK: /help= # Optional val, short and long valid, and delete fish_opt --short h -l help --optional-val --delete @@ -623,7 +628,7 @@ or echo unexpected status $status # Repeated val and short fish_opt -ml help --long-only or echo unexpected status $status -#CHECK: help=+ +#CHECK: /help=+ # Repeated and optional val, short and long but short not valid fish_opt --short h -l help --long-only -mo