diff --git a/po/de.po b/po/de.po index a857bdc2d..73c23f162 100644 --- a/po/de.po +++ b/po/de.po @@ -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 "" diff --git a/po/en.po b/po/en.po index 55979b817..04529cc66 100644 --- a/po/en.po +++ b/po/en.po @@ -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 "" diff --git a/po/fr.po b/po/fr.po index 74e1c7e13..53ecb4a07 100644 --- a/po/fr.po +++ b/po/fr.po @@ -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 "" diff --git a/po/pl.po b/po/pl.po index 3286f0f95..1ff91e2b3 100644 --- a/po/pl.po +++ b/po/pl.po @@ -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 "" diff --git a/po/pt_BR.po b/po/pt_BR.po index ddc672c90..e67b25df8 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -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 "" diff --git a/po/sv.po b/po/sv.po index 053e0ab3a..16adf9ac6 100644 --- a/po/sv.po +++ b/po/sv.po @@ -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 "" diff --git a/po/zh_CN.po b/po/zh_CN.po index 4466dcb16..66ffad875 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -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" diff --git a/src/bin/fish.rs b/src/bin/fish.rs index 963ed2d48..1c29cf00b 100644 --- a/src/bin/fish.rs +++ b/src/bin/fish.rs @@ -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] = &[ 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 { + eprintf!( + "%ls\n", + wgettext_fmt!(BUILTIN_ERR_UNEXP_ARG, "fish", args[w.wopt_index - 1]) + ); + return ControlFlow::Break(1); + } _ => panic!("unexpected retval from WGetopter"), } } diff --git a/src/builtins/abbr.rs b/src/builtins/abbr.rs index 97ec5ed3c..66f2d945c 100644 --- a/src/builtins/abbr.rs +++ b/src/builtins/abbr.rs @@ -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); diff --git a/src/builtins/argparse.rs b/src/builtins/argparse.rs index 1a5b388ba..847b60c35 100644 --- a/src/builtins/argparse.rs +++ b/src/builtins/argparse.rs @@ -83,7 +83,7 @@ fn new() -> Self { } } -const SHORT_OPTIONS: &wstr = L!("+:hn:siux:N:X:"); +const SHORT_OPTIONS: &wstr = L!("+hn:siux:N:X:"); const LONG_OPTIONS: &[WOption] = &[ wopt(L!("stop-nonopt"), ArgType::NoArgument, 's'), wopt(L!("ignore-unknown"), ArgType::NoArgument, 'i'), @@ -574,6 +574,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); @@ -858,7 +868,7 @@ 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); @@ -876,6 +886,16 @@ fn argparse_parse_flags<'args>( ); Err(STATUS_INVALID_ARGS) } + ';' => { + builtin_unexpected_argument( + parser, + streams, + &opts.name, + args_read[w.wopt_index - 1], + false, + ); + Err(STATUS_INVALID_ARGS) + } '?' => { // It's not a recognized flag. See if it's an implicit int flag. let arg_contents = &args_read[w.wopt_index - 1].slice_from(1); diff --git a/src/builtins/bind.rs b/src/builtins/bind.rs index 333ff5c64..748f28aa1 100644 --- a/src/builtins/bind.rs +++ b/src/builtins/bind.rs @@ -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); diff --git a/src/builtins/block.rs b/src/builtins/block.rs index 46f645c66..24805fea1 100644 --- a/src/builtins/block.rs +++ b/src/builtins/block.rs @@ -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); diff --git a/src/builtins/builtin.rs b/src/builtins/builtin.rs index 042da2d97..bdcb513b0 100644 --- a/src/builtins/builtin.rs +++ b/src/builtins/builtin.rs @@ -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); diff --git a/src/builtins/command.rs b/src/builtins/command.rs index 5e17ffb1a..955aae6ad 100644 --- a/src/builtins/command.rs +++ b/src/builtins/command.rs @@ -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); diff --git a/src/builtins/commandline.rs b/src/builtins/commandline.rs index 98d0aed7b..7a6783257 100644 --- a/src/builtins/commandline.rs +++ b/src/builtins/commandline.rs @@ -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); diff --git a/src/builtins/complete.rs b/src/builtins/complete.rs index c42f3aff4..d0c3dfab1 100644 --- a/src/builtins/complete.rs +++ b/src/builtins/complete.rs @@ -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); diff --git a/src/builtins/contains.rs b/src/builtins/contains.rs index 4ce17b8bd..f9ffe8b73 100644 --- a/src/builtins/contains.rs +++ b/src/builtins/contains.rs @@ -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); diff --git a/src/builtins/echo.rs b/src/builtins/echo.rs index 51af381c9..70eaecd0c 100644 --- a/src/builtins/echo.rs +++ b/src/builtins/echo.rs @@ -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)); } diff --git a/src/builtins/fish_key_reader.rs b/src/builtins/fish_key_reader.rs index e9f9b8840..817049faf 100644 --- a/src/builtins/fish_key_reader.rs +++ b/src/builtins/fish_key_reader.rs @@ -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, diff --git a/src/builtins/function.rs b/src/builtins/function.rs index 61c698ea4..fcbf55e4a 100644 --- a/src/builtins/function.rs +++ b/src/builtins/function.rs @@ -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; diff --git a/src/builtins/functions.rs b/src/builtins/functions.rs index 382793474..95d497b98 100644 --- a/src/builtins/functions.rs +++ b/src/builtins/functions.rs @@ -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); diff --git a/src/builtins/history.rs b/src/builtins/history.rs index 8c0834280..bc499e0a4 100644 --- a/src/builtins/history.rs +++ b/src/builtins/history.rs @@ -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..]) { diff --git a/src/builtins/jobs.rs b/src/builtins/jobs.rs index cbbfa1f44..c37e0d844 100644 --- a/src/builtins/jobs.rs +++ b/src/builtins/jobs.rs @@ -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); diff --git a/src/builtins/math.rs b/src/builtins/math.rs index 630b530ae..2e16ce3e2 100644 --- a/src/builtins/math.rs +++ b/src/builtins/math.rs @@ -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. diff --git a/src/builtins/path.rs b/src/builtins/path.rs index d440c795a..b42956f50 100644 --- a/src/builtins/path.rs +++ b/src/builtins/path.rs @@ -178,7 +178,7 @@ fn path_out(streams: &mut IoStreams, opts: &Options<'_>, s: impl AsRef) { 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); diff --git a/src/builtins/pwd.rs b/src/builtins/pwd.rs index 8643bbe06..3cd19bce0 100644 --- a/src/builtins/pwd.rs +++ b/src/builtins/pwd.rs @@ -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); diff --git a/src/builtins/random.rs b/src/builtins/random.rs index 190d39d6b..728291b14 100644 --- a/src/builtins/random.rs +++ b/src/builtins/random.rs @@ -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); diff --git a/src/builtins/read.rs b/src/builtins/read.rs index fe7cb9b6c..31b391aec 100644 --- a/src/builtins/read.rs +++ b/src/builtins/read.rs @@ -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); diff --git a/src/builtins/realpath.rs b/src/builtins/realpath.rs index 628439a4d..1b88aba68 100644 --- a/src/builtins/realpath.rs +++ b/src/builtins/realpath.rs @@ -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); diff --git a/src/builtins/return.rs b/src/builtins/return.rs index 91fa8ac8c..45908c3fc 100644 --- a/src/builtins/return.rs +++ b/src/builtins/return.rs @@ -16,7 +16,7 @@ fn parse_options( ) -> ControlFlow { 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 diff --git a/src/builtins/set.rs b/src/builtins/set.rs index 8a7144f7e..b56501422 100644 --- a/src/builtins/set.rs +++ b/src/builtins/set.rs @@ -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; diff --git a/src/builtins/set_color.rs b/src/builtins/set_color.rs index 358cbd478..a926448d5 100644 --- a/src/builtins/set_color.rs +++ b/src/builtins/set_color.rs @@ -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 diff --git a/src/builtins/shared.rs b/src/builtins/shared.rs index 2ca4510b4..048b4c7b9 100644 --- a/src/builtins/shared.rs +++ b/src/builtins/shared.rs @@ -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, diff --git a/src/builtins/status.rs b/src/builtins/status.rs index ce844d827..047fe4d46 100644 --- a/src/builtins/status.rs +++ b/src/builtins/status.rs @@ -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); diff --git a/src/builtins/string.rs b/src/builtins/string.rs index 2ebe01634..609c29d4b 100644 --- a/src/builtins/string.rs +++ b/src/builtins/string.rs @@ -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); diff --git a/src/builtins/string/collect.rs b/src/builtins/string/collect.rs index daa4076a7..a0740ef3f 100644 --- a/src/builtins/string/collect.rs +++ b/src/builtins/string/collect.rs @@ -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 { diff --git a/src/builtins/string/escape.rs b/src/builtins/string/escape.rs index 0bc08cd74..8463f4f83 100644 --- a/src/builtins/string/escape.rs +++ b/src/builtins/string/escape.rs @@ -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 { diff --git a/src/builtins/string/join.rs b/src/builtins/string/join.rs index fa4b1d71c..57761cff0 100644 --- a/src/builtins/string/join.rs +++ b/src/builtins/string/join.rs @@ -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 { diff --git a/src/builtins/string/length.rs b/src/builtins/string/length.rs index 410c3d260..dc4529cef 100644 --- a/src/builtins/string/length.rs +++ b/src/builtins/string/length.rs @@ -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 { diff --git a/src/builtins/string/match.rs b/src/builtins/string/match.rs index 514be9c81..b5ede546c 100644 --- a/src/builtins/string/match.rs +++ b/src/builtins/string/match.rs @@ -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 { diff --git a/src/builtins/string/pad.rs b/src/builtins/string/pad.rs index 9b704dbf1..1cb2ec1ac 100644 --- a/src/builtins/string/pad.rs +++ b/src/builtins/string/pad.rs @@ -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 { diff --git a/src/builtins/string/repeat.rs b/src/builtins/string/repeat.rs index da3c18856..cd51f4450 100644 --- a/src/builtins/string/repeat.rs +++ b/src/builtins/string/repeat.rs @@ -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 { diff --git a/src/builtins/string/replace.rs b/src/builtins/string/replace.rs index 76192b561..48a41e2ce 100644 --- a/src/builtins/string/replace.rs +++ b/src/builtins/string/replace.rs @@ -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 { diff --git a/src/builtins/string/shorten.rs b/src/builtins/string/shorten.rs index 714426989..271cff99b 100644 --- a/src/builtins/string/shorten.rs +++ b/src/builtins/string/shorten.rs @@ -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, diff --git a/src/builtins/string/split.rs b/src/builtins/string/split.rs index fc6fa5319..a59818749 100644 --- a/src/builtins/string/split.rs +++ b/src/builtins/string/split.rs @@ -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 { diff --git a/src/builtins/string/sub.rs b/src/builtins/string/sub.rs index b945de075..1f17f842c 100644 --- a/src/builtins/string/sub.rs +++ b/src/builtins/string/sub.rs @@ -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 { diff --git a/src/builtins/string/transform.rs b/src/builtins/string/transform.rs index d7ec8b2f2..c148c1114 100644 --- a/src/builtins/string/transform.rs +++ b/src/builtins/string/transform.rs @@ -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, diff --git a/src/builtins/string/trim.rs b/src/builtins/string/trim.rs index f121e28fd..9680532f5 100644 --- a/src/builtins/string/trim.rs +++ b/src/builtins/string/trim.rs @@ -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, diff --git a/src/builtins/string/unescape.rs b/src/builtins/string/unescape.rs index 1a31d72e9..adb6d4152 100644 --- a/src/builtins/string/unescape.rs +++ b/src/builtins/string/unescape.rs @@ -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 { diff --git a/src/builtins/type.rs b/src/builtins/type.rs index 1c44626e1..460e8fbcb 100644 --- a/src/builtins/type.rs +++ b/src/builtins/type.rs @@ -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); diff --git a/src/builtins/ulimit.rs b/src/builtins/ulimit.rs index c800eee4c..9ad0627c3 100644 --- a/src/builtins/ulimit.rs +++ b/src/builtins/ulimit.rs @@ -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); diff --git a/src/builtins/wait.rs b/src/builtins/wait.rs index 0b07c746e..96841d0fb 100644 --- a/src/builtins/wait.rs +++ b/src/builtins/wait.rs @@ -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); diff --git a/src/text_face.rs b/src/text_face.rs index b1e45717b..32aaa32de 100644 --- a/src/text_face.rs +++ b/src/text_face.rs @@ -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, 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)); diff --git a/src/wgetopt.rs b/src/wgetopt.rs index 7110d63ed..42cefe626 100644 --- a/src/wgetopt.rs +++ b/src/wgetopt.rs @@ -137,8 +137,6 @@ 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, /// This will be populated with the elements of the original args that were interpreted @@ -163,13 +161,19 @@ pub fn new( ordering: Ordering::Permute, first_nonopt: 0, last_nonopt: 0, - return_colon: false, initialized: 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 { assert!( self.wopt_index <= self.argv.len(), @@ -233,11 +237,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; } @@ -371,7 +370,7 @@ 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. let val = self.argv[self.wopt_index]; @@ -398,7 +397,7 @@ 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()); } @@ -410,7 +409,7 @@ fn update_long_opt( self.wopt_index += 1; } else { self.remaining_text = empty_wstr(); - return if self.return_colon { ':' } else { '?' }; + return ':'; } } diff --git a/tests/checks/argparse.fish b/tests/checks/argparse.fish index c63a53853..4d2c21d9f 100644 --- a/tests/checks/argparse.fish +++ b/tests/checks/argparse.fish @@ -639,5 +639,11 @@ begin # 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 that the argparse's are properly wrapped in begin blocks set -l diff --git a/tests/checks/bad-option.fish b/tests/checks/bad-option.fish index df47065f6..f2cbcce55 100644 --- a/tests/checks/bad-option.fish +++ b/tests/checks/bad-option.fish @@ -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 diff --git a/tests/checks/functions.fish b/tests/checks/functions.fish index 3c1fd15ba..71b26df92 100644 --- a/tests/checks/functions.fish +++ b/tests/checks/functions.fish @@ -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 diff --git a/tests/checks/line-number.fish b/tests/checks/line-number.fish index c5832db9a..da30a0541 100644 --- a/tests/checks/line-number.fish +++ b/tests/checks/line-number.fish @@ -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 diff --git a/tests/checks/set_color.fish b/tests/checks/set_color.fish index 624081ca3..0154fb660 100644 --- a/tests/checks/set_color.fish +++ b/tests/checks/set_color.fish @@ -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)