error rewrite: use new Error to report errors

To homogenize error reporting format, use a new Error struct. Currently this
is used for builtins and ensuring a common cmd/subcmd prefix.

Part of #12556
This commit is contained in:
Nahor
2026-03-15 17:31:00 -07:00
committed by Johannes Altmanninger
parent abd7442521
commit 30e6aa85e2
77 changed files with 7395 additions and 7680 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -20,11 +20,9 @@
use fish::{
ast,
builtins::{
error::Error,
fish_indent, fish_key_reader,
shared::{
BUILTIN_ERR_MISSING_OPT_ARG, BUILTIN_ERR_UNEXP_OPT_ARG, BUILTIN_ERR_UNKNOWN_OPT,
STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, VERSION_STRING_TEMPLATE,
},
shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, VERSION_STRING_TEMPLATE},
},
common::{
PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME, bytes2wcstring, escape, osstr2wcstring,
@@ -35,12 +33,12 @@
config_paths::ConfigPaths,
environment::{EnvStack, Environment as _, env_init},
},
eprintf,
eprintf, err_fmt,
event::{self, Event},
flog::{self, activate_flog_categories_by_pattern, flog, flogf, set_flog_file_fd},
fprintf, function,
history::{self, start_private_mode},
io::IoChain,
io::{FdOutputStream, IoChain, OutputStream},
locale::set_libc_locales,
nix::isatty,
panic::panic_handler,
@@ -62,7 +60,7 @@
wutil::waccess,
};
use fish_wcstringutil::wcs2bytes;
use libc::STDIN_FILENO;
use libc::{STDERR_FILENO, STDIN_FILENO};
use nix::{
sys::resource::{UsageWho, getrusage},
unistd::{AccessFlags, getpid},
@@ -318,24 +316,24 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
// Either remove it or make it work with flog.
}
'?' => {
eprintf!(
"%s\n\n",
wgettext_fmt!(BUILTIN_ERR_UNKNOWN_OPT, "fish", args[w.wopt_index - 1])
);
err_fmt!(Error::UNKNOWN_OPT, args[w.wopt_index - 1])
.cmd(L!("fish"))
.append_to_msg('\n')
.write_to(&mut OutputStream::Fd(FdOutputStream::new(STDERR_FILENO)));
return ControlFlow::Break(1);
}
':' => {
eprintf!(
"%s\n\n",
wgettext_fmt!(BUILTIN_ERR_MISSING_OPT_ARG, "fish", args[w.wopt_index - 1])
);
err_fmt!(Error::MISSING_OPT_ARG, args[w.wopt_index - 1])
.cmd(L!("fish"))
.append_to_msg('\n')
.write_to(&mut OutputStream::Fd(FdOutputStream::new(STDERR_FILENO)));
return ControlFlow::Break(1);
}
';' => {
eprintf!(
"%s\n\n",
wgettext_fmt!(BUILTIN_ERR_UNEXP_OPT_ARG, "fish", args[w.wopt_index - 1])
);
err_fmt!(Error::UNEXP_OPT_ARG, args[w.wopt_index - 1])
.cmd(L!("fish"))
.append_to_msg('\n')
.write_to(&mut OutputStream::Fd(FdOutputStream::new(STDERR_FILENO)));
return ControlFlow::Break(1);
}
_ => panic!("unexpected retval from WGetopter"),

View File

@@ -1,19 +1,21 @@
use super::prelude::*;
use crate::abbrs::{self, Abbreviation, Position};
use crate::builtins::error::Error;
use crate::common::{EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_func_name};
use crate::env::{EnvMode, EnvStackSetResult};
use crate::highlight::highlight_and_colorize;
use crate::parser::ParserEnvSetMode;
use crate::re::{regex_make_anchored, to_boxed_chars};
use crate::{err_fmt, err_str};
use fish_common::help_section;
use pcre2::utf32::{Regex, RegexBuilder};
localizable_consts! {
NAME_CANNOT_BE_EMPTY
"%s %s: Name cannot be empty"
"Name cannot be empty"
ABBR_CANNOT_HAVE_SPACES
"%s %s: Abbreviation '%s' cannot have spaces in the word"
"Abbreviation '%s' cannot have spaces in the word"
}
const CMD: &wstr = L!("abbr");
@@ -36,7 +38,7 @@ struct Options {
}
impl Options {
fn validate(&mut self, streams: &mut IoStreams) -> bool {
fn validate(&mut self) -> Option<Error<'_>> {
// Duplicate options?
let mut cmds = vec![];
if self.add {
@@ -59,12 +61,7 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
}
if cmds.len() > 1 {
streams.err.appendln(&wgettext_fmt!(
"%s: Cannot combine options %s",
CMD,
join(&cmds, L!(", "))
));
return false;
return Some(err_fmt!("Cannot combine options %s", join(&cmds, L!(", "))));
}
// If run with no options, treat it like --add if we have arguments,
@@ -76,54 +73,29 @@ fn validate(&mut self, streams: &mut IoStreams) -> bool {
localizable_consts! {
OPTION_REQUIRES_ARG
"%s: %s option requires %s"
"%s option requires %s"
}
if !self.add && self.position.is_some() {
streams.err.appendln(&wgettext_fmt!(
OPTION_REQUIRES_ARG,
CMD,
"--position",
"--add",
));
return false;
return Some(err_fmt!(OPTION_REQUIRES_ARG, "--position", "--add"));
}
if !self.add && self.regex_pattern.is_some() {
streams
.err
.appendln(&wgettext_fmt!(OPTION_REQUIRES_ARG, CMD, "--regex", "--add"));
return false;
return Some(err_fmt!(OPTION_REQUIRES_ARG, "--regex", "--add"));
}
if !self.add && self.function.is_some() {
streams.err.appendln(&wgettext_fmt!(
OPTION_REQUIRES_ARG,
CMD,
"--function",
"--add",
));
return false;
return Some(err_fmt!(OPTION_REQUIRES_ARG, "--function", "--add"));
}
if !self.add && self.set_cursor_marker.is_some() {
streams.err.appendln(&wgettext_fmt!(
OPTION_REQUIRES_ARG,
CMD,
"--set-cursor",
"--add",
));
return false;
return Some(err_fmt!(OPTION_REQUIRES_ARG, "--set-cursor", "--add"));
}
if self
.set_cursor_marker
.as_ref()
.is_some_and(|m| m.is_empty())
{
streams.err.appendln(&wgettext_fmt!(
"%s: --set-cursor argument cannot be empty",
CMD
));
return false;
return Some(err_str!("--set-cursor argument cannot be empty"));
}
true
None
}
}
@@ -213,12 +185,9 @@ fn abbr_show(opts: &Options, streams: &mut IoStreams, parser: &Parser) -> Builti
fn abbr_list(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
let subcmd = L!("--list");
if !opts.args.is_empty() {
streams.err.appendln(&wgettext_fmt!(
"%s %s: Unexpected argument -- '%s'",
CMD,
subcmd,
&opts.args[0]
));
err_fmt!("Unexpected argument -- '%s'", &opts.args[0])
.subcmd(CMD, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
abbrs::with_abbrs(|abbrs| {
@@ -237,29 +206,24 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
let subcmd = L!("--rename");
if opts.args.len() != 2 {
streams.err.appendln(&wgettext_fmt!(
"%s %s: Requires exactly two arguments",
CMD,
subcmd
));
err_str!("Requires exactly two arguments")
.subcmd(CMD, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let old_name = &opts.args[0];
let new_name = &opts.args[1];
if old_name.is_empty() || new_name.is_empty() {
streams
.err
.appendln(&wgettext_fmt!(NAME_CANNOT_BE_EMPTY, CMD, subcmd));
err_str!(NAME_CANNOT_BE_EMPTY)
.subcmd(CMD, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if contains_whitespace(new_name) {
streams.err.appendln(&wgettext_fmt!(
ABBR_CANNOT_HAVE_SPACES,
CMD,
subcmd,
new_name.as_utfstr()
));
err_fmt!(ABBR_CANNOT_HAVE_SPACES, new_name.as_utfstr())
.subcmd(CMD, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
abbrs::with_abbrs_mut(|abbrs| -> BuiltinResult {
@@ -268,12 +232,12 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
.iter()
.any(|a| a.name == *old_name && a.commands == opts.commands)
{
streams.err.appendln(&wgettext_fmt!(
"%s %s: No abbreviation named %s with the specified command restrictions",
CMD,
subcmd,
err_fmt!(
"No abbreviation named %s with the specified command restrictions",
old_name.as_utfstr()
));
)
.subcmd(CMD, subcmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
if abbrs
@@ -282,13 +246,13 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
.any(|a| a.name == *new_name && a.commands == opts.commands)
{
if opts.commands.is_empty() {
streams.err.appendln(&wgettext_fmt!(
"%s %s: Abbreviation %s already exists, cannot rename %s",
CMD,
subcmd,
err_fmt!(
"Abbreviation %s already exists, cannot rename %s",
new_name.as_utfstr(),
old_name.as_utfstr()
));
)
.subcmd(CMD, subcmd)
.finish(streams);
} else {
let style = EscapeStringStyle::Script(Default::default());
let mut cmd_list = WString::new();
@@ -299,14 +263,14 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
cmd_list.push_utfstr(&escape_string(cmd, style));
}
streams.err.appendln(&wgettext_fmt!(
"%s %s: Abbreviation %s already exists for commands %s, cannot rename %s",
CMD,
subcmd,
err_fmt!(
"Abbreviation %s already exists for commands %s, cannot rename %s",
new_name.as_utfstr(),
cmd_list.as_utfstr(),
old_name.as_utfstr()
));
)
.subcmd(CMD, subcmd)
.finish(streams);
}
return Err(STATUS_INVALID_ARGS);
@@ -339,28 +303,23 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
let subcmd = L!("--add");
if opts.args.len() < 2 && opts.function.is_none() {
streams.err.appendln(&wgettext_fmt!(
"%s %s: Requires at least two arguments",
CMD,
subcmd
));
err_str!("Requires at least two arguments")
.subcmd(CMD, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if opts.args.is_empty() || opts.args[0].is_empty() {
streams
.err
.appendln(&wgettext_fmt!(NAME_CANNOT_BE_EMPTY, CMD, subcmd));
err_str!(NAME_CANNOT_BE_EMPTY)
.subcmd(CMD, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let name = &opts.args[0];
if name.chars().any(|c| c.is_whitespace()) {
streams.err.appendln(&wgettext_fmt!(
ABBR_CANNOT_HAVE_SPACES,
CMD,
subcmd,
name.as_utfstr()
));
err_fmt!(ABBR_CANNOT_HAVE_SPACES, name.as_utfstr())
.subcmd(CMD, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -376,21 +335,16 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
let result = builder.build(to_boxed_chars(regex_pattern));
if let Err(error) = result {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_REGEX_COMPILE,
CMD,
error.error_message(),
));
let mut err = err_fmt!(Error::REGEX_COMPILE, error.error_message());
if let Some(offset) = error.offset() {
streams
.err
.append(&sprintf!("%s: %s\n", CMD, regex_pattern.as_utfstr()));
err.append_assign_to_msg(&sprintf!("\n%s: %s", CMD, regex_pattern.as_utfstr()));
// TODO: This is misaligned if `regex_pattern` contains characters which are not
// exactly 1 terminal cell wide.
// exactly 1 terminal cell wide or not on a single line.
let mut marker = " ".repeat(offset.saturating_sub(1));
marker.push('^');
streams.err.append(&sprintf!("%s: %s\n", CMD, marker));
err.append_assign_to_msg(&sprintf!("\n%s: %s", CMD, marker));
}
err.cmd(CMD).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let anchored = regex_make_anchored(regex_pattern);
@@ -409,20 +363,16 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
}
if opts.function.is_some() && opts.args.len() > 1 {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, L!("abbr")));
err_str!(Error::TOO_MANY_ARGUMENTS).cmd(CMD).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let replacement = if let Some(ref function) = opts.function {
// Abbreviation function names disallow spaces.
// This is to prevent accidental usage of e.g. `--function 'string replace'`
if !valid_func_name(function) || contains_whitespace(function) {
streams.err.appendln(&wgettext_fmt!(
"%s: Invalid function name: %s",
CMD,
function.as_utfstr()
));
err_fmt!("Invalid function name: %s", function.as_utfstr())
.cmd(CMD)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
function.clone()
@@ -446,10 +396,9 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult {
}
});
if !opts.commands.is_empty() && position == Position::Command {
streams.err.appendln(&wgettext_fmt!(
"%s: --command cannot be combined with --position=command",
CMD,
));
err_str!("--command cannot be combined with --position=command")
.cmd(CMD)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -564,9 +513,9 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
'c' => opts.commands.push(w.woptarg.map(|x| x.to_owned()).unwrap()),
'p' => {
if opts.position.is_some() {
streams
.err
.appendln(&wgettext_fmt!("%s: Cannot specify multiple positions", CMD));
err_str!("Cannot specify multiple positions")
.cmd(CMD)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if w.woptarg == Some(L!("command")) {
@@ -574,37 +523,34 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
} else if w.woptarg == Some(L!("anywhere")) {
opts.position = Some(Position::Anywhere);
} else {
streams.err.appendln(&wgettext_fmt!(
"%s: Invalid position '%s'",
CMD,
w.woptarg.unwrap_or_default()
));
streams.err.appendln(&wgettext_fmt!(
"Position must be one of: %s",
// Use a single argument here to avoid having to update translations when
// the number of options changes.
"command, anywhere",
));
err_fmt!("Invalid position '%s'", w.woptarg.unwrap_or_default())
.append_to_msg('\n')
.append_to_msg(&wgettext_fmt!(
"Position must be one of: %s",
// Use a single argument here to avoid having to update translations when
// the number of options changes.
"command, anywhere",
))
.cmd(CMD)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
'r' => {
if opts.regex_pattern.is_some() {
streams.err.appendln(&wgettext_fmt!(
"%s: Cannot specify multiple regex patterns",
CMD
));
err_str!("Cannot specify multiple regex patterns")
.cmd(CMD)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
opts.regex_pattern = w.woptarg.map(ToOwned::to_owned);
}
SET_CURSOR_SHORT => {
if opts.set_cursor_marker.is_some() {
streams.err.appendln(&wgettext_fmt!(
"%s: Cannot specify multiple set-cursor options",
CMD
));
err_str!("Cannot specify multiple set-cursor options")
.cmd(CMD)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
// The default set-cursor indicator is '%'.
@@ -624,19 +570,20 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
'U' => {
// Kept and made ineffective, so we warn.
streams.err.append(&wgettext_fmt!(
"%s: Warning: Option '%s' was removed and is now ignored",
cmd,
err_fmt!(
"Warning: Option '%s' was removed and is now ignored",
argv_read[w.wopt_index - 1]
));
builtin_print_error_trailer(parser, streams.err, cmd);
)
.cmd(CMD)
.full_trailer(parser)
.finish(streams);
}
'h' => {
builtin_print_help(parser, streams, cmd);
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -660,7 +607,8 @@ pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
opts.args.push((*arg).into());
}
if !opts.validate(streams) {
if let Some(err) = opts.validate() {
err.cmd(cmd).finish(streams);
return Err(STATUS_INVALID_ARGS);
}

View File

@@ -2,19 +2,21 @@
use super::prelude::*;
use crate::builtins::error::Error;
use crate::env::{EnvMode, EnvSetMode, EnvStack};
use crate::exec::exec_subshell;
use crate::parser::ParserEnvSetMode;
use crate::wutil::fish_iswalnum;
use crate::{err_fmt, err_str};
const VAR_NAME_PREFIX: &wstr = L!("_flag_");
localizable_consts!(
BUILTIN_ERR_INVALID_OPT_SPEC
"%s: Invalid option spec '%s' at char '%c'"
"Invalid option spec '%s' at char '%c'"
MISSING_DOUBLE_HYPHEN_SEPARATOR
"%s: Missing -- separator"
"Missing -- separator"
);
#[derive(Default)]
@@ -149,12 +151,9 @@ fn check_for_mutually_exclusive_flags(
if flag1 > flag2 {
std::mem::swap(&mut flag1, &mut flag2);
}
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
opts.name,
flag1,
flag2
));
err_fmt!(Error::COMBO_EXCLUSIVE, flag1, flag2)
.cmd(&opts.name)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -171,11 +170,9 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
for raw_xflags in &opts.raw_exclusive_flags {
let xflags: Vec<_> = raw_xflags.split(',').collect();
if xflags.len() < 2 {
streams.err.appendln(&wgettext_fmt!(
"%s: exclusive flag string '%s' is not valid",
opts.name,
raw_xflags
));
err_fmt!("exclusive flag string '%s' is not valid", raw_xflags)
.cmd(&opts.name)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
@@ -189,11 +186,9 @@ fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) ->
// It's a long flag we store as its short flag equivalent.
exclusive_set.push(*short_equiv);
} else {
streams.err.appendln(&wgettext_fmt!(
"%s: exclusive flag '%s' is not valid",
opts.name,
flag
));
err_fmt!("exclusive flag '%s' is not valid", flag)
.cmd(&opts.name)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
}
@@ -218,12 +213,13 @@ fn parse_flag_modifiers<'args>(
&& s.char_at(0) != '!'
&& s.char_at(0) != '&'
{
streams.err.appendln(&wgettext_fmt!(
"%s: Implicit int short flag '%c' does not allow modifiers like '%c'",
opts.name,
err_fmt!(
"Implicit int short flag '%c' does not allow modifiers like '%c'",
opt_spec.short_flag,
s.char_at(0)
));
)
.cmd(&opts.name)
.finish(streams);
return false;
}
@@ -253,24 +249,18 @@ fn parse_flag_modifiers<'args>(
if s.char_at(0) == '!' {
if opt_spec.arg_type == ArgType::NoArgument {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_INVALID_OPT_SPEC,
opts.name,
option_spec,
s.char_at(0)
));
err_fmt!(BUILTIN_ERR_INVALID_OPT_SPEC, option_spec, s.char_at(0))
.cmd(&opts.name)
.finish(streams);
}
s = s.slice_from(1);
opt_spec.validation_command = s;
// Move cursor to the end so we don't expect a long flag.
s = s.slice_from(s.char_count());
} else if !s.is_empty() {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_INVALID_OPT_SPEC,
opts.name,
option_spec,
s.char_at(0)
));
err_fmt!(BUILTIN_ERR_INVALID_OPT_SPEC, option_spec, s.char_at(0))
.cmd(&opts.name)
.finish(streams);
return false;
}
@@ -280,11 +270,9 @@ fn parse_flag_modifiers<'args>(
}
if opts.options.contains_key(&opt_spec.short_flag) {
streams.err.appendln(&wgettext_fmt!(
"%s: Short flag '%c' already defined",
opts.name,
opt_spec.short_flag
));
err_fmt!("Short flag '%c' already defined", opt_spec.short_flag)
.cmd(&opts.name)
.finish(streams);
return false;
}
@@ -303,7 +291,7 @@ fn parse_option_spec_sep<'args>(
) -> bool {
localizable_consts! {
IMPLICIT_INT_FLAG_ALREADY_DEFINED
"%s: Implicit int flag '%c' already defined"
"Implicit int flag '%c' already defined"
}
let mut s = *opt_spec_str;
let mut i = 1usize;
@@ -316,11 +304,9 @@ fn parse_option_spec_sep<'args>(
*counter += 1;
}
if opts.implicit_int_flag != '\0' {
streams.err.appendln(&wgettext_fmt!(
IMPLICIT_INT_FLAG_ALREADY_DEFINED,
opts.name,
opts.implicit_int_flag
));
err_fmt!(IMPLICIT_INT_FLAG_ALREADY_DEFINED, opts.implicit_int_flag)
.cmd(&opts.name)
.finish(streams);
return false;
}
opts.implicit_int_flag = opt_spec.short_flag;
@@ -335,34 +321,26 @@ fn parse_option_spec_sep<'args>(
opt_spec.short_flag_valid = false;
i += 1;
if i == s.char_count() {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_INVALID_OPT_SPEC,
opts.name,
option_spec,
s.char_at(i - 1)
));
err_fmt!(BUILTIN_ERR_INVALID_OPT_SPEC, option_spec, s.char_at(i - 1))
.cmd(&opts.name)
.finish(streams);
return false;
}
}
'/' => {
i += 1; // the struct is initialized assuming short_flag_valid should be true
if i == s.char_count() {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_INVALID_OPT_SPEC,
opts.name,
option_spec,
s.char_at(i - 1)
));
err_fmt!(BUILTIN_ERR_INVALID_OPT_SPEC, option_spec, s.char_at(i - 1))
.cmd(&opts.name)
.finish(streams);
return false;
}
}
'#' => {
if opts.implicit_int_flag != '\0' {
streams.err.appendln(&wgettext_fmt!(
IMPLICIT_INT_FLAG_ALREADY_DEFINED,
opts.name,
opts.implicit_int_flag
));
err_fmt!(IMPLICIT_INT_FLAG_ALREADY_DEFINED, opts.implicit_int_flag)
.cmd(&opts.name)
.finish(streams);
return false;
}
opts.implicit_int_flag = opt_spec.short_flag;
@@ -402,21 +380,21 @@ fn parse_option_spec<'args>(
streams: &mut IoStreams,
) -> bool {
if option_spec.is_empty() {
streams.err.appendln(&wgettext_fmt!(
"%s: An option spec must have at least a short or a long flag",
opts.name
));
err_str!("An option spec must have at least a short or a long flag")
.cmd(&opts.name)
.finish(streams);
return false;
}
let mut s = option_spec;
if !fish_iswalnum(s.char_at(0)) && s.char_at(0) != '#' && !(s.char_at(0) == '/' && s.len() > 1)
{
streams.err.appendln(&wgettext_fmt!(
"%s: Short flag '%c' invalid, must be alphanum or '#'",
opts.name,
err_fmt!(
"Short flag '%c' invalid, must be alphanum or '#'",
s.char_at(0)
));
)
.cmd(&opts.name)
.finish(streams);
return false;
}
@@ -439,11 +417,9 @@ fn parse_option_spec<'args>(
if long_flag_char_count > 0 {
opt_spec.long_flag = s.slice_to(long_flag_char_count);
if opts.long_to_short_flag.contains_key(opt_spec.long_flag) {
streams.err.appendln(&wgettext_fmt!(
"%s: Long flag '%s' already defined",
opts.name,
opt_spec.long_flag
));
err_fmt!("Long flag '%s' already defined", opt_spec.long_flag)
.cmd(&opts.name)
.finish(streams);
return false;
}
}
@@ -486,9 +462,9 @@ fn collect_option_specs<'args>(
loop {
if *optind == argc {
streams
.err
.appendln(&wgettext_fmt!(MISSING_DOUBLE_HYPHEN_SEPARATOR, cmd));
err_str!(MISSING_DOUBLE_HYPHEN_SEPARATOR)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -508,9 +484,9 @@ fn collect_option_specs<'args>(
let counter_max = 0xF8FFu32;
if counter > counter_max {
streams
.err
.appendln(&wgettext_fmt!("%s: Too many long-only options", cmd));
err_str!("Too many long-only options")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -540,12 +516,9 @@ fn parse_cmd_opts<'args>(
's' => opts.stop_nonopt = true,
'i' | 'u' => {
if opts.unknown_handling != UnknownHandling::Error {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
"--ignore-unknown",
"--move-unknown"
));
err_fmt!(Error::COMBO_EXCLUSIVE, "--ignore-unknown", "--move-unknown")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
opts.unknown_handling = if c == 'i' {
@@ -564,11 +537,9 @@ fn parse_cmd_opts<'args>(
} else if kind == L!("none") {
ArgType::NoArgument
} else {
streams.err.appendln(&wgettext_fmt!(
"%s: Invalid --unknown-arguments value '%s'",
cmd,
kind
));
err_fmt!("Invalid --unknown-arguments value '%s'", kind)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -581,11 +552,9 @@ fn parse_cmd_opts<'args>(
opts.min_args = {
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
if x < 0 {
streams.err.appendln(&wgettext_fmt!(
"%s: Invalid --min-args value '%s'",
cmd,
w.woptarg.unwrap()
));
err_fmt!("Invalid --min-args value '%s'", w.woptarg.unwrap())
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
x.try_into().unwrap()
@@ -595,11 +564,9 @@ fn parse_cmd_opts<'args>(
opts.max_args = {
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
if x < 0 {
streams.err.appendln(&wgettext_fmt!(
"%s: Invalid --max-args value '%s'",
cmd,
w.woptarg.unwrap()
));
err_fmt!("Invalid --max-args value '%s'", w.woptarg.unwrap())
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
x.try_into().unwrap()
@@ -610,6 +577,7 @@ fn parse_cmd_opts<'args>(
parser,
streams,
cmd,
None,
args[w.wopt_index - 1],
/* print_hints */ false,
);
@@ -648,9 +616,9 @@ fn parse_cmd_opts<'args>(
if argc == w.wopt_index {
// The user didn't specify any option specs.
streams
.err
.appendln(&wgettext_fmt!(MISSING_DOUBLE_HYPHEN_SEPARATOR, cmd));
err_str!(MISSING_DOUBLE_HYPHEN_SEPARATOR)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -932,6 +900,7 @@ fn argparse_parse_flags<'args>(
parser,
streams,
&opts.name,
None,
args_read[w.wopt_index - 1],
false,
);
@@ -961,11 +930,9 @@ fn argparse_parse_flags<'args>(
streams,
)?;
} else if opts.unknown_handling == UnknownHandling::Error {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_UNKNOWN_OPT,
opts.name,
args_read[w.wopt_index - 1]
));
err_fmt!(Error::UNKNOWN_OPT, args_read[w.wopt_index - 1])
.cmd(&opts.name)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
} else {
// The option is unknown, so there's no long opt index it could have used
@@ -1016,11 +983,9 @@ fn argparse_parse_flags<'args>(
Some(w.argv[w.wopt_index - 1])
} else {
// the option is at the end of argv, so it has no argument
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_MISSING_OPT_ARG,
opts.name,
args_read[w.wopt_index - 1]
));
err_fmt!(Error::MISSING_OPT_ARG, args_read[w.wopt_index - 1])
.cmd(&opts.name)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
} else {
@@ -1032,11 +997,9 @@ fn argparse_parse_flags<'args>(
&& is_long_flag
&& arg_contents.contains('=')
{
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_UNEXP_OPT_ARG,
opts.name,
args_read[w.wopt_index - 1]
));
err_fmt!(Error::UNEXP_OPT_ARG, args_read[w.wopt_index - 1])
.cmd(&opts.name)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -1129,22 +1092,16 @@ fn check_min_max_args_constraints(
let cmd = &opts.name;
if opts.args.len() < opts.min_args {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_MIN_ARG_COUNT1,
cmd,
opts.min_args,
opts.args.len()
));
err_fmt!(Error::MIN_ARG_COUNT, opts.min_args, opts.args.len())
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
if opts.max_args != usize::MAX && opts.args.len() > opts.max_args {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_MAX_ARG_COUNT1,
cmd,
opts.max_args,
opts.args.len()
));
err_fmt!(Error::MAX_ARG_COUNT, opts.max_args, opts.args.len())
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}

View File

@@ -2,7 +2,7 @@
use std::{collections::HashSet, rc::Rc};
use crate::proc::Pid;
use crate::{builtins::error::Error, err_fmt, err_str, proc::Pid};
use super::prelude::*;
@@ -17,12 +17,13 @@ fn send_to_bg(
let jobs = parser.jobs();
if !jobs[job_pos].wants_job_control() {
let job = &jobs[job_pos];
streams.err.appendln(&wgettext_fmt!(
"%s: Can't put job %s, '%s' to background because it is not under job control",
cmd,
err_fmt!(
"Can't put job %s, '%s' to background because it is not under job control",
job.job_id().to_wstring(),
job.command()
));
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
@@ -66,9 +67,7 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
};
let Some(job_pos) = job_pos else {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NO_SUITABLE_JOBS, cmd));
err_str!(Error::NO_SUITABLE_JOBS).cmd(cmd).finish(streams);
return Err(STATUS_CMD_ERROR);
};
@@ -101,9 +100,9 @@ pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
send_to_bg(parser, streams, cmd, job_pos)?;
}
} else {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_COULD_NOT_FIND_JOB, cmd, pid));
err_fmt!(Error::COULD_NOT_FIND_JOB, pid)
.cmd(cmd)
.finish(streams);
}
}

View File

@@ -1,6 +1,7 @@
//! Implementation of the bind builtin.
use super::prelude::*;
use crate::builtins::error::Error;
use crate::common::{
EscapeFlags, EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_var_name,
};
@@ -11,6 +12,7 @@
use crate::key::{
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
};
use crate::{err_fmt, err_raw, err_str};
use fish_common::help_section;
use std::sync::MutexGuard;
@@ -263,7 +265,7 @@ fn compute_seq(&self, streams: &mut IoStreams, seq: &wstr) -> Option<Vec<Key>> {
match parse_keys(seq) {
Ok(keys) => Some(keys),
Err(err) => {
streams.err.append(&sprintf!("bind: %s\n", err));
err_raw!(err).cmd(L!("bind")).finish(streams);
None
}
}
@@ -314,12 +316,9 @@ fn insert(
} else {
// Inserting both on the other hand makes no sense.
if self.opts.have_preset && self.opts.have_user {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
"--preset",
"--user"
));
err_fmt!(Error::COMBO_EXCLUSIVE, "--preset", "--user")
.cmd(cmd)
.finish(streams);
return true;
}
}
@@ -355,17 +354,13 @@ fn insert(
);
if !self.opts.silent {
if seq.len() == 1 {
streams.err.appendln(&wgettext_fmt!(
"%s: No binding found for key '%s'",
cmd,
seq[0]
));
err_fmt!("No binding found for key '%s'", seq[0])
.cmd(cmd)
.finish(streams);
} else {
streams.err.appendln(&wgettext_fmt!(
"%s: No binding found for key sequence '%s'",
cmd,
eseq
));
err_fmt!("No binding found for key sequence '%s'", eseq)
.cmd(cmd)
.finish(streams);
}
}
return true;
@@ -435,12 +430,13 @@ fn parse_cmd_opts(
let check_mode_name = |streams: &mut IoStreams, mode_name: &wstr| -> Result<(), ErrorCode> {
if !valid_var_name(mode_name) {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_BIND_MODE,
cmd,
err_fmt!(
Error::BIND_MODE,
mode_name,
help_section!("language#shell-variable-and-function-names")
));
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
Ok(())
@@ -454,10 +450,11 @@ fn parse_cmd_opts(
'f' => opts.mode = BindMode::FunctionNames,
'h' => opts.print_help = true,
'k' => {
streams.err.appendln(&wgettext_fmt!(
"%s: the -k/--key syntax is no longer supported. See `bind --help` and `bind --key-names`",
cmd,
));
err_str!(
"the -k/--key syntax is no longer supported. See `bind --help` and `bind --key-names`"
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
'K' => opts.mode = BindMode::KeyNames,
@@ -485,7 +482,7 @@ fn parse_cmd_opts(
opts.user = true;
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {

View File

@@ -1,5 +1,7 @@
use std::sync::atomic::Ordering;
use crate::err_str;
// Implementation of the block builtin.
use super::prelude::*;
@@ -51,7 +53,7 @@ fn parse_options(
opts.erase = true;
}
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -84,17 +86,14 @@ pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
if opts.erase {
if opts.scope != Scope::Unset {
streams.err.appendln(&wgettext_fmt!(
"%s: Can not specify scope when removing block",
cmd
));
err_str!("Can not specify scope when removing block")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if parser.global_event_blocks.load(Ordering::Relaxed) == 0 {
streams
.err
.appendln(&wgettext_fmt!("%s: No blocks defined", cmd));
err_str!("No blocks defined").cmd(cmd).finish(streams);
return Err(STATUS_CMD_ERROR);
}
parser.global_event_blocks.fetch_sub(1, Ordering::Relaxed);

View File

@@ -1,18 +1,17 @@
use super::prelude::*;
use crate::builtins::error::Error;
use crate::parser::{Block, BlockType};
use crate::reader::reader_read;
use crate::{err_fmt, err_str};
use libc::STDIN_FILENO;
/// Implementation of the builtin breakpoint command, used to launch the interactive debugger.
pub fn breakpoint(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
let cmd = argv[0];
if argv.len() != 1 {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT1,
cmd,
0,
argv.len() - 1
));
err_fmt!(Error::UNEXP_ARG_COUNT, 0, argv.len() - 1)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -28,10 +27,9 @@ pub fn breakpoint(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr])
.block_at_index(1)
.is_none_or(|b| b.typ() == BlockType::breakpoint)
{
streams.err.appendln(&wgettext_fmt!(
"%s: Command not valid at an interactive prompt",
cmd,
));
err_str!("Command not valid at an interactive prompt")
.cmd(cmd)
.finish(streams);
return Err(STATUS_ILLEGAL_CMD);
}
}

View File

@@ -1,3 +1,5 @@
use crate::{builtins::error::Error, err_fmt};
use super::prelude::*;
#[derive(Default)]
@@ -29,7 +31,14 @@ pub fn r#builtin(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
builtin_missing_argument(
parser,
streams,
cmd,
None,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -53,11 +62,12 @@ pub fn r#builtin(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
}
if opts.query && opts.list_names {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
wgettext!("--query and --names are mutually exclusive")
));
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}

View File

@@ -3,13 +3,13 @@
use super::prelude::*;
use crate::{
env::{EnvMode, Environment as _},
err_fmt, err_raw, err_str,
fds::{BEST_O_SEARCH, wopen_dir},
parser::ParserEnvSetMode,
path::path_apply_cdpath,
wutil::{normalize_path, wreadlink},
};
use errno::Errno;
use fish_util::perror;
use libc::{EACCES, ELOOP, ENOENT, ENOTDIR, EPERM};
use nix::unistd::fchdir;
use std::sync::Arc;
@@ -19,7 +19,7 @@
pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult {
localizable_consts! {
DIR_DOES_NOT_EXIST
"%s: The directory '%s' does not exist"
"The directory '%s' does not exist"
}
let Some(&cmd) = args.first() else {
@@ -45,9 +45,9 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
&tmpstr
}
None => {
streams
.err
.appendln(&wgettext_fmt!("%s: Could not find home directory", cmd));
err_str!("Could not find home directory")
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
}
@@ -55,14 +55,11 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
// Stop `cd ""` from crashing
if dir_in.is_empty() {
streams.err.appendln(&wgettext_fmt!(
"%s: Empty directory '%s' does not exist",
cmd,
dir_in
));
let mut err = err_fmt!("Empty directory '%s' does not exist", dir_in).cmd(cmd);
if !parser.is_interactive() {
streams.err.append(&parser.current_line());
err = err.stacktrace(parser);
}
err.finish(streams);
return Err(STATUS_CMD_ERROR);
}
@@ -135,44 +132,30 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Built
return Ok(SUCCESS);
}
if best_errno == ENOTDIR {
streams
.err
.appendln(&wgettext_fmt!("%s: '%s' is not a directory", cmd, dir_in));
let mut err = if best_errno == ENOTDIR {
err_fmt!("'%s' is not a directory", dir_in)
} else if !broken_symlink.is_empty() {
streams.err.appendln(&wgettext_fmt!(
"%s: '%s' is a broken symbolic link to '%s'",
cmd,
err_fmt!(
"'%s' is a broken symbolic link to '%s'",
broken_symlink,
broken_symlink_target
));
)
} else if best_errno == ELOOP {
streams.err.appendln(&wgettext_fmt!(
"%s: Too many levels of symbolic links: '%s'",
cmd,
dir_in
));
err_fmt!("Too many levels of symbolic links: '%s'", dir_in)
} else if best_errno == ENOENT {
streams
.err
.appendln(&wgettext_fmt!(DIR_DOES_NOT_EXIST, cmd, dir_in));
err_fmt!(DIR_DOES_NOT_EXIST, dir_in)
} else if best_errno == EACCES || best_errno == EPERM {
streams
.err
.appendln(&wgettext_fmt!("%s: Permission denied: '%s'", cmd, dir_in));
err_fmt!("Permission denied: '%s'", dir_in)
} else {
errno::set_errno(Errno(best_errno));
perror("cd");
streams.err.appendln(&wgettext_fmt!(
"%s: Unknown error trying to locate directory '%s'",
cmd,
dir_in
));
}
err_raw!(builtin_strerror()).cmd(L!("cd")).finish(streams);
err_fmt!("Unknown error trying to locate directory '%s'", dir_in)
};
if !parser.is_interactive() {
streams.err.append(&parser.current_line());
err = err.stacktrace(parser);
}
err.cmd(cmd).finish(streams);
Err(STATUS_CMD_ERROR)
}

View File

@@ -36,7 +36,14 @@ pub fn r#command(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
builtin_missing_argument(
parser,
streams,
cmd,
None,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {

View File

@@ -1,6 +1,7 @@
use super::prelude::*;
use super::read::TokenOutputMode;
use crate::ast::{self, Kind, Leaf as _};
use crate::builtins::error::Error;
use crate::common::{UnescapeFlags, UnescapeStringStyle, unescape_string};
use crate::complete::Completion;
use crate::expand::{ExpandFlags, ExpandResultCode, expand_string};
@@ -12,7 +13,6 @@
detect_parse_errors, get_job_extent, get_offset_from_line, get_process_extent,
get_token_extent, lineno,
};
use crate::prelude::*;
use crate::proc::is_interactive_session;
use crate::reader::{
JumpDirection, JumpPrecision, commandline_get_state, commandline_set_buffer,
@@ -20,6 +20,7 @@
reader_showing_suggestion,
};
use crate::tokenizer::{TOK_ACCEPT_UNFINISHED, TokenType, Tokenizer};
use crate::{err_fmt, err_str, prelude::*};
use fish_wcstringutil::join_strings;
use std::ops::Range;
@@ -320,12 +321,13 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
'f' => function_mode = true,
'x' | '\x02' | 'o' => {
if token_mode.is_some() {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
wgettext!("--tokens options are mutually exclusive")
));
builtin_print_error_trailer(parser, streams.err, cmd);
)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
token_mode = Some(match c {
@@ -372,7 +374,14 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
builtin_missing_argument(
parser,
streams,
cmd,
None,
w.argv[w.wopt_index - 1],
true,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -424,23 +433,25 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|| selection_start_mode
|| selection_end_mode
{
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if positional_args == 0 {
builtin_missing_argument(parser, streams, cmd, L!("--function"), true);
builtin_missing_argument(parser, streams, cmd, None, L!("--function"), true);
return Err(STATUS_INVALID_ARGS);
}
type RL = ReadlineCmd;
for arg in &w.argv[w.wopt_index..] {
let Some(cmd) = input_function_get_code(arg) else {
streams
.err
.append(&wgettext_fmt!("%s: Unknown input function '%s'", cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!("Unknown input function '%s'", arg)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
// Don't enqueue a repaint if we're currently in the middle of one,
@@ -467,20 +478,20 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
// Check for invalid switch combinations.
if (selection_start_mode || selection_end_mode) && positional_args != 0 {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::TOO_MANY_ARGUMENTS)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if (search_mode || line_mode || column_mode || cursor_mode || paging_mode)
&& positional_args > 1
{
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::TOO_MANY_ARGUMENTS)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -489,24 +500,29 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
// Special case - we allow to get/set cursor position relative to the process/job/token.
&& ((buffer_part.is_none() && !search_field_mode) || !cursor_mode)
{
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if (token_mode.is_some() || cut_at_cursor) && positional_args != 0 {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
"--cut-at-cursor and token options can not be used when setting the commandline"
));
builtin_print_error_trailer(parser, streams.err, cmd);
)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if search_field_mode && (buffer_part.is_some() || token_mode.is_some()) {
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -522,26 +538,20 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
if append_mode == AppendMode::InsertSmart {
if search_field_mode {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
"--insert-smart",
"--search-field"
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::COMBO_EXCLUSIVE, "--insert-smart", "--search-field")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
match buffer_part {
TextScope::String | TextScope::Job | TextScope::Process => (),
TextScope::Token => {
// To-do: we can support it in command position.
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
"--insert-smart",
"--current-token"
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::COMBO_EXCLUSIVE, "--insert-smart", "--current-token")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -552,19 +562,19 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let arg = w.argv[w.wopt_index];
let new_coord = match fish_wcstol(arg) {
Err(_) => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::NOT_NUMBER, arg)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
0
}
Ok(num) => num - 1,
};
let Ok(new_coord) = usize::try_from(new_coord) else {
streams
.err
.append(&wgettext_fmt!("%s: line/column index starts at 1", cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("line/column index starts at 1")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
@@ -572,10 +582,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let Some(offset) =
get_offset_from_line(&rstate.text, i32::try_from(new_coord).unwrap())
else {
streams
.err
.appendln(&wgettext_fmt!("%s: there is no line %s", cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!("there is no line %s", arg)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
offset
@@ -587,12 +597,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let next_line_offset =
get_offset_from_line(&rstate.text, line_index + 1).unwrap_or(rstate.text.len());
if line_offset + new_coord > next_line_offset {
streams.err.appendln(&wgettext_fmt!(
"%s: column %s exceeds line length",
cmd,
arg
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!("column %s exceeds line length", arg)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
line_offset + new_coord
@@ -675,11 +683,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
current_cursor_pos = current_buffer.len();
} else if parser.libdata().transient_commandline.is_some() {
if cursor_mode && positional_args != 0 {
streams.err.append(&wgettext_fmt!(
"%s: setting cursor while evaluating 'complete --arguments' is not yet supported",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("setting cursor while evaluating 'complete --arguments' is not yet supported")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
transient = parser.libdata().transient_commandline.clone().unwrap();
@@ -690,11 +697,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
current_cursor_pos = rstate.cursor_pos;
} else {
// There is no command line because we are not interactive.
streams.err.append(cmd);
streams
.err
.append(L!(": Can not set commandline in non-interactive mode\n"));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("Can not set commandline in non-interactive mode")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
@@ -742,10 +748,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
let arg = w.argv[w.wopt_index];
let new_pos = match fish_wcstol(arg) {
Err(_) => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::NOT_NUMBER, arg)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
0
}
Ok(num) => num,

View File

@@ -1,4 +1,5 @@
use super::prelude::*;
use crate::builtins::error::Error;
use crate::common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string};
use crate::complete::{CompletionRequestOptions, complete_add_wrapper, complete_remove_wrapper};
use crate::highlight::highlight_and_colorize;
@@ -15,6 +16,7 @@
complete_remove, complete_remove_all,
},
};
use crate::{err_fmt, err_raw, err_str};
use fish_wcstringutil::string_suffixes_string;
// builtin_complete_* are a set of rather silly looping functions that make sure that all the proper
@@ -245,7 +247,7 @@ fn builtin_complete_print(
pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
localizable_consts! {
OPTION_REQUIRES_NON_EMPTY_STRING
"%s: %s requires a non-empty string"
"%s requires a non-empty string"
}
let cmd = argv[0];
@@ -326,11 +328,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
cmd_to_complete.push(tmp);
}
} else {
streams.err.appendln(&wgettext_fmt!(
"%s: Invalid token '%s'",
cmd,
w.woptarg.unwrap()
));
err_fmt!("Invalid token '%s'", w.woptarg.unwrap())
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -347,11 +347,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
let arg = w.woptarg.unwrap();
short_opt.extend(arg.chars());
if arg.is_empty() {
streams.err.appendln(&wgettext_fmt!(
OPTION_REQUIRES_NON_EMPTY_STRING,
cmd,
"-s",
));
err_fmt!(OPTION_REQUIRES_NON_EMPTY_STRING, "-s",)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -359,11 +357,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
let arg = w.woptarg.unwrap();
gnu_opt.push(arg);
if arg.is_empty() {
streams.err.appendln(&wgettext_fmt!(
OPTION_REQUIRES_NON_EMPTY_STRING,
cmd,
"-l",
));
err_fmt!(OPTION_REQUIRES_NON_EMPTY_STRING, "-l",)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -371,11 +367,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
let arg = w.woptarg.unwrap();
old_opt.push(arg);
if arg.is_empty() {
streams.err.appendln(&wgettext_fmt!(
OPTION_REQUIRES_NON_EMPTY_STRING,
cmd,
"-o",
));
err_fmt!(OPTION_REQUIRES_NON_EMPTY_STRING, "-o",)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -404,7 +398,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -424,19 +418,21 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if result_mode.no_files && result_mode.force_files {
if !have_x {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
"complete",
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
"'--no-files' and '--force-files'"
));
)
.cmd(cmd)
.finish(streams);
} else {
// The reason for us not wanting files is `-x`,
// which is short for `-rf`.
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
"complete",
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
"'--exclusive' and '--force-files'"
));
)
.cmd(cmd)
.finish(streams);
}
return Err(STATUS_INVALID_ARGS);
}
@@ -450,10 +446,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
// Or use one left-over arg as the command to complete
cmd_to_complete.push(argv[argc - 1].to_owned());
} else {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::TOO_MANY_ARGUMENTS)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -461,14 +457,16 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
for condition_string in &condition {
let mut errors = ParseErrorList::new();
if detect_parse_errors(condition_string, Some(&mut errors), false).is_err() {
let prefix = WString::from(L!("-n '")) + &condition_string[..] + L!("'");
for error in errors {
let prefix = cmd.to_owned() + L!(": -n '") + &condition_string[..] + L!("'");
streams.err.appendln(&error.describe_with_prefix(
err_raw!(&error.describe_with_prefix(
condition_string,
&prefix,
parser.is_interactive(),
false,
));
))
.cmd(cmd)
.finish(streams);
}
return Err(STATUS_CMD_ERROR);
}
@@ -476,10 +474,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
if !comp.is_empty() {
if let Err(err_text) = detect_errors_in_argument_list(&comp, cmd) {
streams
.err
.appendln(&wgettext_fmt!("%s: %s: contains a syntax error", cmd, comp));
streams.err.appendln(&err_text);
let mut err = err_fmt!("%s: contains a syntax error", comp);
err.append_assign_to_msg('\n');
err.append_assign_to_msg(&err_text);
err.cmd(cmd).finish(streams);
return Err(STATUS_CMD_ERROR);
}
}
@@ -491,10 +489,9 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) ->
// No argument given, try to use the current commandline.
let commandline_state = commandline_get_state(true);
if !parser.interactive_initialized.load() && !is_interactive_session() {
streams.err.append(cmd);
streams
.err
.append(L!(": Can not get commandline in non-interactive mode\n"));
err_str!("Can not get commandline in non-interactive mode")
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
commandline_state.text

View File

@@ -1,3 +1,5 @@
use crate::err_str;
// Implementation of the contains builtin.
use super::prelude::*;
@@ -28,7 +30,7 @@ fn parse_options(
'h' => opts.print_help = true,
'i' => opts.print_index = true,
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -71,9 +73,7 @@ pub fn contains(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
}
}
} else {
streams
.err
.appendln(&wgettext_fmt!("%s: Key not specified", cmd));
err_str!("Key not specified").cmd(cmd).finish(streams);
}
Err(STATUS_CMD_ERROR)

View File

@@ -1,10 +1,12 @@
// Implementation of the disown builtin.
use super::prelude::*;
use crate::builtins::error::Error;
use crate::builtins::shared::HelpOnlyCmdOpts;
use crate::io::IoStreams;
use crate::parser::Parser;
use crate::proc::{Job, add_disowned_job};
use crate::{builtins::shared::HelpOnlyCmdOpts, localization::wgettext_fmt};
use crate::{err_fmt, err_str};
use fish_widestring::wstr;
use nix::sys::signal::{Signal, killpg};
@@ -21,12 +23,13 @@ fn disown_job(cmd: &wstr, streams: &mut IoStreams, j: &Job) {
if let Some(pgid) = pgid {
let _ = killpg(pgid.as_nix_pid(), Some(Signal::SIGCONT));
}
streams.err.appendln(&wgettext_fmt!(
"%s: job %d ('%s') was stopped and has been signalled to continue.",
cmd,
err_fmt!(
"job %d ('%s') was stopped and has been signalled to continue.",
j.job_id(),
j.command()
));
)
.cmd(cmd)
.finish(streams);
}
// We cannot directly remove the job from the jobs() list as `disown` might be called
@@ -64,9 +67,7 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
disown_job(cmd, streams, &job);
retval = Ok(SUCCESS);
} else {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NO_SUITABLE_JOBS, cmd));
err_str!(Error::NO_SUITABLE_JOBS).cmd(cmd).finish(streams);
retval = Err(STATUS_CMD_ERROR);
}
} else {
@@ -87,9 +88,9 @@ pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
};
parser.job_get_from_pid(pid).or_else(|| {
// Valid identifier but no such job
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_COULD_NOT_FIND_JOB, cmd, pid));
err_fmt!(Error::COULD_NOT_FIND_JOB, pid)
.cmd(cmd)
.finish(streams);
None
})
})

View File

@@ -45,7 +45,7 @@ fn parse_options(
's' => opts.print_spaces = false,
'E' => opts.interpret_special_chars = false,
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {

View File

@@ -1,5 +1,5 @@
use super::prelude::*;
use crate::event;
use crate::{err_str, event};
pub fn emit(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult {
let Some(&cmd) = argv.first() else {
@@ -14,9 +14,7 @@ pub fn emit(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
}
let Some(event_name) = argv.get(opts.optind) else {
streams
.err
.append(&sprintf!(L!("%s: expected event name\n"), cmd));
err_str!("expected event name").cmd(cmd).finish(streams);
return Err(STATUS_INVALID_ARGS);
};

View File

@@ -1,10 +1,12 @@
//! Implementation of the fg builtin.
use crate::builtins::error::Error;
use crate::fds::make_fd_blocking;
use crate::parser::ParserEnvSetMode;
use crate::reader::{reader_save_screen_state, reader_write_title};
use crate::tokenizer::tok_command;
use crate::{env::EnvMode, tty_handoff::TtyHandoff};
use crate::{err_fmt, err_str};
use fish_util::perror;
use libc::STDIN_FILENO;
use nix::sys::termios::{self, tcsetattr};
@@ -38,9 +40,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
&& ((job.is_stopped() || !job.is_foreground()) && job.wants_job_control())
}) {
None => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NO_SUITABLE_JOBS, cmd));
err_str!(Error::NO_SUITABLE_JOBS).cmd(cmd).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
Some((pos, j)) => {
@@ -57,17 +57,13 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
found_job = parser.job_get_from_pid(pid).is_some();
}
if found_job {
streams
.err
.appendln(&wgettext_fmt!("%s: Ambiguous job", cmd));
let err = if found_job {
err_str!("Ambiguous job")
} else {
streams
.err
.appendln(&wgettext_fmt!("%s: '%s' is not a job", cmd, argv[optind]));
}
err_fmt!("'%s' is not a job", argv[optind])
};
err.cmd(cmd).full_trailer(parser).finish(streams);
builtin_print_error_trailer(parser, streams.err, cmd);
job_pos = None;
job = None;
} else {
@@ -77,21 +73,21 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
if j.as_ref()
.is_none_or(|(_pos, j)| !j.is_constructed() || j.is_completed())
{
streams
.err
.appendln(&wgettext_fmt!("%s: No suitable job: %d", cmd, pid));
err_fmt!("No suitable job: %d", pid)
.cmd(cmd)
.finish(streams);
job_pos = None;
job = None;
} else {
let (pos, j) = j.unwrap();
job_pos = Some(pos);
job = if !j.wants_job_control() {
streams.err.appendln(&wgettext_fmt!(
"%s: Can't put job %d, '%s' to foreground because it is not under job control",
cmd,
err_fmt!("Can't put job %d, '%s' to foreground because it is not under job control",
pid,
j.command()
));
)
.cmd(cmd)
.finish(streams);
None
} else {
Some(j)

View File

@@ -5,6 +5,7 @@
use std::io::{Read, Write as _};
use std::os::unix::ffi::OsStrExt as _;
use crate::builtins::error::Error;
use crate::locale::set_libc_locales;
use crate::panic::panic_handler;
@@ -19,18 +20,19 @@
use crate::env::EnvStack;
use crate::env::env_init;
use crate::env::environment::Environment as _;
use crate::err_fmt;
use crate::expand::INTERNAL_SEPARATOR;
use crate::global_safety::RelaxedAtomicBool;
use crate::highlight::{HighlightRole, HighlightSpec, colorize, highlight_shell};
use crate::operation_context::OperationContext;
use crate::parse_constants::{ParseTokenType, ParseTreeFlags, SourceRange};
use crate::parse_util::{SPACES_PER_INDENT, apply_indents, compute_indents};
use crate::prelude::*;
use crate::print_help::print_help;
use crate::threads;
use crate::tokenizer::{TOK_SHOW_BLANK_LINES, TOK_SHOW_COMMENTS, TokenType, Tokenizer};
use crate::topic_monitor::topic_monitor_init;
use crate::wutil::fish_iswalnum;
use crate::{err_str, prelude::*};
use assert_matches::assert_matches;
use fish_wcstringutil::{count_preceding_backslashes, wcs2bytes};
use fish_wgetopt::{ArgType, WGetopter, WOption, wopt};
@@ -1025,19 +1027,15 @@ enum OutputType {
'\x03' => output_type = OutputType::PygmentsCsv,
'c' => output_type = OutputType::Check,
';' => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_UNEXP_OPT_ARG,
"fish_indent",
w.argv[w.wopt_index - 1]
));
err_fmt!(Error::UNEXP_OPT_ARG, w.argv[w.wopt_index - 1])
.cmd(L!("fish_indent"))
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
'?' => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_UNKNOWN_OPT,
"fish_indent",
w.argv[w.wopt_index - 1]
));
err_fmt!(Error::UNKNOWN_OPT, w.argv[w.wopt_index - 1])
.cmd(L!("fish_indent"))
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
_ => panic!(),
@@ -1053,18 +1051,14 @@ enum OutputType {
while i < args.len() || (args.is_empty() && i == 0) {
if args.is_empty() && i == 0 {
if output_type == OutputType::File {
streams.err.append(&sprintf!(
"%s\n\n $ %s -w foo.fish\n",
wgettext!("Expected file path to read/write for -w:"),
get_program_name()
));
err_str!("Expected file path to read/write for -w:")
.append_to_msg(&sprintf!("\n\n $ %s -w foo.fish\n", get_program_name()))
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
let Some(stdin_file) = streams.stdin_file.as_mut() else {
let cmd = "fish_indent";
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_STDIN_CLOSED, cmd));
let cmd = L!("fish_indent");
err_str!(Error::STDIN_CLOSED).cmd(cmd).finish(streams);
return Err(STATUS_CMD_ERROR);
};
let mut buf = vec![];
@@ -1090,11 +1084,7 @@ enum OutputType {
output_location = arg;
}
Err(err) => {
streams.err.appendln(&wgettext_fmt!(
"Opening \"%s\" failed: %s",
arg,
err.to_string()
));
err_fmt!("Opening \"%s\" failed: %s", arg, err.to_string()).finish(streams);
return Err(STATUS_CMD_ERROR);
}
}
@@ -1171,11 +1161,12 @@ enum OutputType {
let _ = file.write_all(&wcs2bytes(&output_wtext));
}
Err(err) => {
streams.err.appendln(&wgettext_fmt!(
err_fmt!(
"Opening \"%s\" failed: %s",
output_location,
err.to_string()
));
)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
}

View File

@@ -12,9 +12,10 @@
use libc::{STDIN_FILENO, VEOF, VINTR};
use crate::{
builtins::shared::BUILTIN_ERR_UNKNOWN_OPT,
builtins::error::Error,
common::{PROGRAM_NAME, get_program_name, osstr2wcstring, shell_modes},
env::{EnvStack, Environment as _, env_init},
err_fmt, err_str,
input_common::{
CharEvent, ImplicitEvent, InputEventQueue, InputEventQueuer as _, KeyEvent,
QueryResultEvent, match_key_event_to_key,
@@ -211,19 +212,15 @@ fn parse_flags(
*verbose = true;
}
';' => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_UNEXP_OPT_ARG,
"fish_key_reader",
w.argv[w.wopt_index - 1]
));
err_fmt!(Error::UNEXP_OPT_ARG, w.argv[w.wopt_index - 1])
.cmd(L!("fish_key_reader"))
.finish(streams);
return ControlFlow::Break(Err(STATUS_CMD_ERROR));
}
'?' => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_UNKNOWN_OPT,
"fish_key_reader",
w.argv[w.wopt_index - 1]
));
err_fmt!(Error::UNKNOWN_OPT, w.argv[w.wopt_index - 1])
.cmd(L!("fish_key_reader"))
.finish(streams);
return ControlFlow::Break(Err(STATUS_CMD_ERROR));
}
_ => panic!(),
@@ -232,9 +229,7 @@ fn parse_flags(
let argc = args.len() - w.wopt_index;
if argc != 0 {
streams
.err
.appendln(&wgettext_fmt!("Expected no arguments, got %d", argc));
err_fmt!("Expected no arguments, got %d", argc).finish(streams);
return ControlFlow::Break(Err(STATUS_CMD_ERROR));
}
@@ -261,7 +256,7 @@ pub fn fish_key_reader(
}
if streams.stdin_fd() < 0 || !isatty(streams.stdin_fd()) {
streams.err.appendln("Stdin must be attached to a tty.");
err_str!("Stdin must be attached to a tty.").finish(streams);
return Err(STATUS_CMD_ERROR);
}
@@ -301,22 +296,20 @@ fn throwing_main() -> i32 {
let mut out = Fd(FdOutputStream::new(STDOUT_FILENO));
let mut err = Fd(FdOutputStream::new(STDERR_FILENO));
let io_chain = IoChain::new();
let mut streams = IoStreams::new(&mut out, &mut err, &io_chain);
let streams = &mut IoStreams::new(&mut out, &mut err, &io_chain);
let mut continuous_mode = false;
let mut verbose = false;
let args: Vec<WString> = std::env::args_os().map(osstr2wcstring).collect();
if let ControlFlow::Break(s) =
parse_flags(None, &mut streams, args, &mut continuous_mode, &mut verbose)
parse_flags(None, streams, args, &mut continuous_mode, &mut verbose)
{
return s.builtin_status_code();
}
if !isatty(STDIN_FILENO) {
streams
.err
.appendln(wgettext!("Stdin must be attached to a tty."));
err_str!("Stdin must be attached to a tty.").finish(streams);
return 1;
}
@@ -327,6 +320,5 @@ fn throwing_main() -> i32 {
terminal_init(&vars, STDIN_FILENO).input_queue
};
setup_and_process_keys(&mut streams, continuous_mode, verbose, input_queue)
.builtin_status_code()
setup_and_process_keys(streams, continuous_mode, verbose, input_queue).builtin_status_code()
}

View File

@@ -5,13 +5,13 @@
use crate::env::environment::Environment as _;
use crate::env::is_read_only;
use crate::event::{self, EventDescription, EventHandler};
use crate::function;
use crate::global_safety::RelaxedAtomicBool;
use crate::parse_execution::varname_error;
use crate::parse_tree::NodeRef;
use crate::parser_keywords::parser_keywords_is_reserved;
use crate::proc::Pid;
use crate::signal::Signal;
use crate::{err_fmt, err_str, function};
use nix::unistd::getpid;
use std::sync::Arc;
@@ -86,15 +86,13 @@ fn parse_cmd_opts(
let mut validate_variable_name =
|streams: &mut IoStreams, varname: &wstr, read_only_ok: bool| {
if !valid_var_name(varname) {
streams.err.append(&varname_error(cmd, varname));
varname_error(cmd, varname).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if !read_only_ok && is_read_only(varname) {
streams.err.appendln(&wgettext_fmt!(
"%s: variable '%s' is read-only",
cmd,
varname
));
err_fmt!("variable '%s' is read-only", varname)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
Ok(())
@@ -122,11 +120,9 @@ fn add_named_argument(
if handling_named_arguments {
add_named_argument(&mut validate_variable_name, streams, opts, woptarg)?;
} else {
streams.err.append(&wgettext_fmt!(
"%s: %s: unexpected positional argument",
cmd,
woptarg
));
err_fmt!("%s: unexpected positional argument", woptarg)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -135,11 +131,9 @@ fn add_named_argument(
}
's' => {
let Some(signal) = Signal::parse(w.woptarg.unwrap()) else {
streams.err.append(&wgettext_fmt!(
"%s: Unknown signal '%s'",
cmd,
w.woptarg.unwrap()
));
err_fmt!("Unknown signal '%s'", w.woptarg.unwrap())
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
opts.events.push(EventDescription::Signal { signal });
@@ -163,10 +157,9 @@ fn add_named_argument(
0
};
if caller_id == 0 {
streams.err.append(&wgettext_fmt!(
"%s: calling job for event handler not found",
cmd
));
err_str!("calling job for event handler not found")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
e = EventDescription::CallerExit { caller_id };
@@ -214,7 +207,14 @@ fn add_named_argument(
opts.print_help = true;
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
builtin_missing_argument(
parser,
streams,
cmd,
None,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -245,11 +245,9 @@ fn add_named_argument(
add_named_argument(&mut validate_variable_name, streams, opts, arg)?;
}
} else {
streams.err.append(&wgettext_fmt!(
"%s: %s: unexpected positional argument",
cmd,
argv[optind],
));
err_fmt!("%s: unexpected positional argument", argv[optind],)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -271,19 +269,18 @@ fn validate_function_name(
}
*function_name = argv[1].to_owned();
if !valid_func_name(function_name) {
streams.err.append(&wgettext_fmt!(
"%s: %s: invalid function name",
cmd,
function_name,
));
err_fmt!("%s: invalid function name", function_name,)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if parser_keywords_is_reserved(function_name) {
streams.err.append(&wgettext_fmt!(
"%s: %s: cannot use reserved keyword as function name",
cmd,
err_fmt!(
"%s: cannot use reserved keyword as function name",
function_name
));
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
Ok(SUCCESS)

View File

@@ -1,9 +1,12 @@
use super::prelude::*;
use crate::builtins::error::Error;
use crate::common::bytes2wcstring;
use crate::common::escape_string;
use crate::common::reformat_for_screen;
use crate::common::valid_func_name;
use crate::common::{EscapeFlags, EscapeStringStyle};
use crate::err_fmt;
use crate::err_str;
use crate::event::{self};
use crate::function;
use crate::highlight::highlight_and_colorize;
@@ -84,7 +87,14 @@ fn parse_cmd_opts<'args>(
opts.color = ColorEnabled::parse_from_opt(streams, cmd, w.woptarg.unwrap())?;
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
builtin_missing_argument(
parser,
streams,
cmd,
None,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -118,7 +128,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
localizable_consts! {
FUNCTION_DOES_NOT_EXIST
"%s: Function '%s' does not exist"
"Function '%s' does not exist"
}
let mut opts = FunctionsCmdOpts::default();
@@ -141,14 +151,18 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
.count()
> 1
{
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if opts.report_metadata && opts.no_metadata {
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -162,20 +176,19 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if let Some(desc) = opts.description {
if args.len() != 1 {
streams.err.appendln(&wgettext_fmt!(
"%s: Expected exactly one function name",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("Expected exactly one function name")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let current_func = args[0];
if !function::exists(current_func, parser) {
streams
.err
.appendln(&wgettext_fmt!(FUNCTION_DOES_NOT_EXIST, cmd, current_func));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(FUNCTION_DOES_NOT_EXIST, current_func)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
@@ -185,9 +198,8 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if opts.report_metadata {
if args.len() != 1 {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT2,
cmd,
err_fmt!(
Error::UNPEXP_ARG_COUNT_WITH_CTX,
// This error is
// functions: --details: expected 1 arguments; got 2
// The "--details" was "argv[optind - 1]" in the C++
@@ -196,7 +208,9 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
"--details",
1,
args.len()
));
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let props = function::get_props_autoload(args[0], parser);
@@ -268,12 +282,13 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if !opts.handlers_type.unwrap_or(L!("")).is_empty()
&& !event::EVENT_FILTER_NAMES.contains(&opts.handlers_type.unwrap())
{
streams.err.appendln(&wgettext_fmt!(
"%s: Expected %s for %s",
cmd,
err_fmt!(
"Expected %s for %s",
"generic | variable | signal | exit | job-id",
"--handlers-type",
));
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
event::print(streams, opts.handlers_type.unwrap_or(L!("")));
@@ -310,42 +325,40 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -
if opts.copy {
if args.len() != 2 {
streams.err.appendln(&wgettext_fmt!(
"%s: Expected exactly two names (current function name, and new function name)",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("Expected exactly two names (current function name, and new function name)")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let current_func = args[0];
let new_func = args[1];
if !function::exists(current_func, parser) {
streams
.err
.appendln(&wgettext_fmt!(FUNCTION_DOES_NOT_EXIST, cmd, current_func));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(FUNCTION_DOES_NOT_EXIST, current_func)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
if !valid_func_name(new_func) || parser_keywords_is_reserved(new_func) {
streams.err.appendln(&wgettext_fmt!(
"%s: Illegal function name '%s'",
cmd,
new_func
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!("Illegal function name '%s'", new_func)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if function::exists(new_func, parser) {
streams.err.appendln(&wgettext_fmt!(
"%s: Function '%s' already exists. Cannot create copy of '%s'",
cmd,
err_fmt!(
"Function '%s' already exists. Cannot create copy of '%s'",
new_func,
current_func
));
builtin_print_error_trailer(parser, streams.err, cmd);
)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
if function::copy(current_func, new_func.into(), parser) {

View File

@@ -1,8 +1,10 @@
//! Implementation of the history builtin.
use crate::builtins::error::Error;
use crate::history::in_private_mode;
use crate::history::{self, History, history_session_id};
use crate::reader::commandline_get_state;
use crate::{err_fmt, err_str};
use super::prelude::*;
@@ -94,12 +96,13 @@ fn set_hist_cmd(
streams: &mut IoStreams,
) -> bool {
if *hist_cmd != HistCmd::None {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
err_fmt!(
Error::COMBO_EXCLUSIVE,
hist_cmd.to_wstr(),
sub_cmd.to_wstr()
));
)
.cmd(cmd)
.finish(streams);
return false;
}
*hist_cmd = sub_cmd;
@@ -114,22 +117,16 @@ fn check_for_unexpected_hist_args(
) -> bool {
if opts.search_type.is_some() || opts.show_time_format.is_some() || opts.null_terminate {
let subcmd_str = opts.hist_cmd.to_wstr();
streams.err.appendln(&wgettext_fmt!(
"%s: %s: subcommand takes no options",
cmd,
subcmd_str
));
err_str!("subcommand takes no options")
.subcmd(cmd, subcmd_str)
.finish(streams);
return true;
}
if !args.is_empty() {
let subcmd_str = opts.hist_cmd.to_wstr();
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT2,
cmd,
subcmd_str,
0,
args.len()
));
err_fmt!(Error::UNEXP_ARG_COUNT, 0, args.len())
.subcmd(cmd, subcmd_str)
.finish(streams);
return true;
}
false
@@ -195,11 +192,9 @@ fn parse_cmd_opts(
'n' => match fish_wcstol(w.woptarg.unwrap()) {
Ok(x) => opts.max_items = Some(x as _), // todo!("historical behavior is to cast")
Err(_) => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_NOT_NUMBER,
cmd,
w.woptarg.unwrap()
));
err_fmt!(Error::NOT_NUMBER, w.woptarg.unwrap())
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
},
@@ -210,7 +205,7 @@ fn parse_cmd_opts(
opts.print_help = true;
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -305,15 +300,12 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
// be handled only by the history function's interactive delete feature.
if opts.search_type.unwrap_or(history::SearchType::Exact) != history::SearchType::Exact
{
streams
.err
.appendln(wgettext!("builtin history delete only supports --exact"));
err_str!("builtin history delete only supports --exact").finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if !opts.case_sensitive {
streams.err.appendln(wgettext!(
"builtin history delete --exact requires --case-sensitive"
));
err_str!("builtin history delete --exact requires --case-sensitive")
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -341,10 +333,9 @@ pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
}
if in_private_mode(parser.vars()) {
streams.err.appendln(&wgettext_fmt!(
"%s: can't merge history in private mode",
cmd
));
err_str!("can't merge history in private mode")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
history.incorporate_external_changes();

View File

@@ -2,6 +2,7 @@
use super::prelude::*;
use crate::common::{EscapeFlags, EscapeStringStyle, escape_string, timef};
use crate::err_fmt;
use crate::io::IoStreams;
use crate::job_group::{JobId, MaybeJobId};
use crate::localization::{wgettext, wgettext_fmt};
@@ -170,7 +171,7 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], true);
builtin_missing_argument(parser, streams, cmd, None, argv[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -202,11 +203,9 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
if arg.char_at(0) == '%' {
match fish_wcstoi(&arg[1..]).ok().filter(|&job_id| job_id >= 0) {
None => {
streams.err.appendln(&wgettext_fmt!(
"%s: '%s' is not a valid job ID",
cmd,
arg
));
err_fmt!("'%s' is not a valid job ID", arg)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
Some(job_id) => {
@@ -230,9 +229,9 @@ pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
found = true;
} else {
if mode != JobsPrintMode::PrintNothing {
streams
.err
.appendln(&wgettext_fmt!("%s: No suitable job: %s", cmd, arg));
err_fmt!("No suitable job: %s", arg)
.cmd(cmd)
.finish(streams);
}
return Err(STATUS_CMD_ERROR);
}

View File

@@ -2,7 +2,7 @@
use num_traits::pow;
use super::prelude::*;
use crate::tinyexpr::te_interp;
use crate::{builtins::error::Error, err_fmt, tinyexpr::te_interp};
/// The maximum number of points after the decimal that we'll print.
const DEFAULT_SCALE: usize = 6;
@@ -67,9 +67,9 @@ fn parse_cmd_opts(
} else {
let scale = fish_wcstoi(optarg).unwrap_or(-1);
if scale < 0 || scale > 15 {
streams
.err
.appendln(&wgettext_fmt!("%s: %s: invalid scale", cmd, optarg));
err_fmt!("%s: invalid scale", optarg)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
// We know the value is in the range [0, 15]
@@ -87,9 +87,9 @@ fn parse_cmd_opts(
} else if optarg.eq(L!("ceiling")) || optarg.eq(L!("ceil")) {
opts.scale_mode = ScaleMode::Ceiling;
} else {
streams
.err
.appendln(&wgettext_fmt!("%s: %s: invalid mode", cmd, optarg));
err_fmt!("%s: invalid mode", optarg)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -102,11 +102,9 @@ fn parse_cmd_opts(
} else {
let base = fish_wcstoi(optarg).unwrap_or(-1);
if base != 8 && base != 16 {
streams.err.appendln(&wgettext_fmt!(
"%s: %s: invalid base value",
cmd,
optarg
));
err_fmt!("%s: invalid base value", optarg)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
// We know the value is 8 or 16.
@@ -117,7 +115,14 @@ fn parse_cmd_opts(
opts.print_help = true;
}
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], print_hints);
builtin_missing_argument(
parser,
streams,
cmd,
None,
args[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -142,11 +147,12 @@ fn parse_cmd_opts(
}
if have_scale && opts.scale != 0 && opts.base != 10 {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
wgettext!("non-zero scale value only valid for base 10")
));
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
"non-zero scale value only valid for base 10"
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -242,27 +248,23 @@ fn evaluate_expression(
return Ok(SUCCESS);
};
streams
.err
.append(&sprintf!("%s: Error: %s\n", cmd, error_message));
streams.err.append(&sprintf!("'%s'\n", expression));
let mut err = err_fmt!("Error: %s", error_message);
err.append_assign_to_msg(&sprintf!("\n'%s'\n", expression));
err.cmd(cmd).finish(streams);
Err(STATUS_CMD_ERROR)
}
Err(err) => {
streams.err.append(&sprintf!(
L!("%s: Error: %s\n"),
cmd,
err.kind.describe_wstr()
));
streams.err.append(&sprintf!("'%s'\n", expression));
let mut error = err_fmt!("Error: %s", err.kind.describe_wstr());
error.append_assign_to_msg(&sprintf!("\n'%s'", expression));
let padding = WString::from_chars(vec![' '; err.position + 1]);
if err.len >= 2 {
let tildes = WString::from_chars(vec!['~'; err.len - 2]);
streams.err.append(&sprintf!("%s^%s^\n", padding, tildes));
error.append_assign_to_msg(&sprintf!("\n%s^%s^", padding, tildes));
} else {
streams.err.append(&sprintf!("%s^\n", padding));
error.append_assign_to_msg(&sprintf!("\n%s^", padding));
}
error.cmd(cmd).finish(streams);
Err(STATUS_CMD_ERROR)
}
@@ -292,9 +294,9 @@ pub fn math(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
}
if expression.is_empty() {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_MIN_ARG_COUNT1, cmd, 1, 0));
err_fmt!(Error::MIN_ARG_COUNT, 1, 0)
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}

View File

@@ -66,3 +66,5 @@ mod prelude {
NON_OPTION_CHAR, WGetopter, WOption, wopt,
};
}
pub use shared::*;

View File

@@ -1,4 +1,6 @@
use crate::builtins::error::Error;
use crate::env::environment::Environment as _;
use crate::{err_fmt, err_str};
use std::fs::Metadata;
use std::os::unix::prelude::{FileTypeExt as _, MetadataExt as _};
use std::time::SystemTime;
@@ -15,23 +17,6 @@
use libc::{PATH_MAX, S_ISGID, S_ISUID, mode_t};
use nix::unistd::{AccessFlags, Gid, Uid};
macro_rules! path_error {
(
$streams:expr,
$string:expr
$(, $args:expr)+
$(,)?
) => {
$streams.err.append(L!("path "));
$streams.err.appendln(&wgettext_fmt!($string, $($args),*));
};
}
fn path_unknown_option(parser: &Parser, streams: &mut IoStreams, subcmd: &wstr, opt: &wstr) {
path_error!(streams, BUILTIN_ERR_UNKNOWN_OPT, subcmd, opt);
builtin_print_error_trailer(parser, streams.err, L!("path"));
}
// How many bytes we read() at once.
// We use PATH_MAX here so we always get at least one path,
// and so we can automatically detect NULL-separated input.
@@ -233,7 +218,8 @@ fn parse_opts<'args>(
parser: &Parser,
streams: &mut IoStreams,
) -> BuiltinResult {
let cmd = args[0];
let cmd = L!("path");
let subcmd = args[0];
let mut args_read = Vec::with_capacity(args.len());
args_read.extend_from_slice(args);
@@ -243,23 +229,27 @@ fn parse_opts<'args>(
while let Some(c) = w.next_opt() {
match c {
':' => {
streams.err.append(L!("path ")); // clone of string_error
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(
builtin_missing_argument(
parser,
streams,
cmd,
Some(subcmd),
args_read[w.wopt_index - 1],
false,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
err_fmt!(Error::UNEXP_OPT_ARG, args_read[w.wopt_index - 1])
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
path_unknown_option(parser, streams, cmd, args_read[w.wopt_index - 1]);
err_fmt!(Error::UNKNOWN_OPT, args_read[w.wopt_index - 1])
.subcmd(cmd, subcmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
'q' => {
@@ -283,7 +273,9 @@ fn parse_opts<'args>(
let types_args = split_string_tok(w.woptarg.unwrap(), L!(","), None);
for t in types_args {
let Ok(r#type) = t.try_into() else {
path_error!(streams, "%s: Invalid type '%s'", cmd, t);
err_fmt!("Invalid type '%s'", t)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
*types |= r#type;
@@ -295,7 +287,9 @@ fn parse_opts<'args>(
let perms_args = split_string_tok(w.woptarg.unwrap(), L!(","), None);
for p in perms_args {
let Ok(perm) = p.try_into() else {
path_error!(streams, "%s: Invalid permission '%s'", cmd, p);
err_fmt!("Invalid permission '%s'", p)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
*perms |= perm;
@@ -359,7 +353,10 @@ fn parse_opts<'args>(
continue;
}
_ => {
path_unknown_option(parser, streams, cmd, args_read[w.wopt_index - 1]);
err_fmt!(Error::UNKNOWN_OPT, args_read[w.wopt_index - 1])
.subcmd(cmd, subcmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -375,14 +372,18 @@ fn parse_opts<'args>(
}
if opts.arg1.is_none() && n_req_args == 1 {
path_error!(streams, BUILTIN_ERR_ARG_COUNT0, cmd);
err_str!(Error::MISSING_ARG)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
// At this point we should not have optional args and be reading args from stdin.
if streams.stdin_is_directly_redirected && args.len() > *optind {
path_error!(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd);
err_str!(Error::TOO_MANY_ARGUMENTS)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -698,7 +699,9 @@ fn path_sort(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu
}
None => wbasename,
Some(k) => {
path_error!(streams, "%s: Invalid sort key '%s'", args[0], k);
err_fmt!("Invalid sort key '%s'", k)
.subcmd(L!("path"), args[0])
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
};
@@ -949,10 +952,10 @@ pub fn path(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui
};
let argc = args.len();
if argc <= 1 {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING_SUBCMD, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::MISSING_SUBCMD)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -975,10 +978,10 @@ pub fn path(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui
"resolve" => path_resolve,
"sort" => path_sort,
_ => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_INVALID_SUBCMD, cmd, subcmd_name));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_SUBCMD)
.subcmd(cmd, subcmd_name)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
};

View File

@@ -49,6 +49,7 @@
// This file has been imported from source code of printf command in GNU Coreutils version 6.9.
use super::prelude::*;
use crate::builtins::error;
use crate::locale::{Locale, get_numeric_locale};
use crate::wutil::{
errors::Error,
@@ -56,6 +57,7 @@
wcstoi::{Options as WcstoiOpts, wcstoi_partial},
wstr_offset_in,
};
use crate::{err_fmt, err_str};
use fish_printf::{ToArg as _, sprintf_locale};
use fish_widestring::{decode_byte_from_char, encode_byte_to_char};
@@ -208,7 +210,7 @@ fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
if errcode.is_some_and(|err| err != Error::InvalidChar && err != Error::Empty) {
match errcode.unwrap() {
Error::Overflow => {
self.fatal_error(wgettext_fmt!("%s: Number out of range", s));
self.fatal_error(err_fmt!("%s: Number out of range", s));
}
Error::InvalidChar | Error::Empty => {
unreachable!("Unreachable");
@@ -216,10 +218,10 @@ fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
}
} else if !end.is_empty() {
if s.as_ptr() == end.as_ptr() {
self.fatal_error(wgettext_fmt!("%s: expected a numeric value", s));
self.fatal_error(err_fmt!("%s: expected a numeric value", s));
} else {
// This isn't entirely fatal - the value should still be printed.
self.nonfatal_error(wgettext_fmt!(
self.nonfatal_error(err_fmt!(
"%s: value not completely converted (can't convert '%s')",
s,
end
@@ -228,7 +230,7 @@ fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
// Do it if the unconverted digit is a valid hex digit,
// because it could also be an "0x" -> "0" typo.
if s.char_at(0) == '0' && iswxdigit(end.char_at(0)) {
self.nonfatal_error(wgettext!(
self.nonfatal_error(err_str!(
"Hint: a leading '0' without an 'x' indicates an octal number"
));
}
@@ -238,7 +240,7 @@ fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<Error>) {
fn handle_sprintf_error(&mut self, err: fish_printf::Error) {
match err {
fish_printf::Error::Overflow => self.fatal_error(wgettext!("Number out of range")),
fish_printf::Error::Overflow => self.fatal_error(err_str!("Number out of range")),
_ => panic!("unhandled error: {err:?}"),
}
}
@@ -469,7 +471,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
if (c_int::MIN as i64) <= width && width <= (c_int::MAX as i64) {
field_width = Some(width);
} else {
self.fatal_error(wgettext_fmt!("invalid field width: %s", argv[0]));
self.fatal_error(err_fmt!("invalid field width: %s", argv[0]));
}
argv = &argv[1..];
argc -= 1;
@@ -497,10 +499,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
// so -1 is safe here even if prec < INT_MIN.
precision = Some(-1);
} else if (c_int::MAX as i64) < prec {
self.fatal_error(wgettext_fmt!(
"invalid precision: %s",
argv[0]
));
self.fatal_error(err_fmt!("invalid precision: %s", argv[0]));
} else {
precision = Some(prec);
}
@@ -526,7 +525,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
let directive = &directive_start[0..directive_start
.len()
.min(wstr_offset_in(f, directive_start) + 1)];
self.fatal_error(wgettext_fmt!(
self.fatal_error(err_fmt!(
"%s: invalid conversion specification",
directive
));
@@ -560,8 +559,7 @@ fn print_formatted(&mut self, format: &wstr, mut argv: &[&wstr]) -> usize {
save_argc - argc
}
fn nonfatal_error<Str: AsRef<wstr>>(&mut self, errstr: Str) {
let errstr = errstr.as_ref();
fn nonfatal_error(&mut self, err: error::Error) {
// Don't error twice.
if self.early_exit {
return;
@@ -573,7 +571,8 @@ fn nonfatal_error<Str: AsRef<wstr>>(&mut self, errstr: Str) {
self.buff.clear();
}
self.streams.err.append(errstr);
let errstr = err.to_string();
self.streams.err.append(&errstr);
if !errstr.ends_with('\n') {
self.streams.err.append('\n');
}
@@ -583,8 +582,8 @@ fn nonfatal_error<Str: AsRef<wstr>>(&mut self, errstr: Str) {
self.exit_code = Err(STATUS_CMD_ERROR);
}
fn fatal_error<Str: AsRef<wstr>>(&mut self, errstr: Str) {
self.nonfatal_error(errstr);
fn fatal_error(&mut self, err: error::Error) {
self.nonfatal_error(err);
self.early_exit = true;
}
@@ -608,7 +607,7 @@ fn print_esc(&mut self, escstart: &wstr, octal_0: bool) -> usize {
p = &p[1..];
}
if esc_length == 0 {
self.fatal_error(wgettext!("missing hexadecimal number in escape"));
self.fatal_error(err_str!("missing hexadecimal number in escape"));
}
self.append_output(encode_byte_to_char((esc_value % 256) as u8));
} else if is_octal_digit(p.char_at(0)) {
@@ -637,7 +636,7 @@ fn print_esc(&mut self, escstart: &wstr, octal_0: bool) -> usize {
if !iswxdigit(p.char_at(0)) {
// Escape sequence must be done. Complain if we didn't get anything.
if esc_length == 0 {
self.fatal_error(wgettext!("Missing hexadecimal number in Unicode escape"));
self.fatal_error(err_str!("Missing hexadecimal number in Unicode escape"));
}
break;
}
@@ -660,7 +659,7 @@ fn print_esc(&mut self, escstart: &wstr, octal_0: bool) -> usize {
}
None => {
let escaped_char_string = format!("\\{esc_char}{uni_value:0exp_esc_length$x}");
self.fatal_error(wgettext_fmt!(
self.fatal_error(err_fmt!(
"Not a valid Unicode character: %s",
escaped_char_string
));

View File

@@ -2,7 +2,7 @@
use errno::errno;
use super::prelude::*;
use crate::{env::Environment as _, wutil::wrealpath};
use crate::{builtins::error::Error, env::Environment as _, err_fmt, wutil::wrealpath};
// The pwd builtin. Respect -P to resolve symbolic links. Respect -L to not do that (the default).
const short_options: &wstr = L!("LPh");
@@ -38,9 +38,9 @@ pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Buil
}
if w.wopt_index != argc {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_ARG_COUNT1, cmd, 0, argc - 1));
err_fmt!(Error::UNEXP_ARG_COUNT, 0, argc - 1)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -52,12 +52,9 @@ pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Buil
if let Some(real_pwd) = wrealpath(&pwd) {
pwd = real_pwd;
} else {
streams.err.appendln(&wgettext_fmt!(
"%s: %s failed: %s",
cmd,
"realpath",
errno().to_string()
));
err_fmt!("%s failed: %s", "realpath", errno().to_string())
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
}

View File

@@ -1,6 +1,7 @@
use super::prelude::*;
use crate::wutil;
use crate::builtins::error::Error;
use crate::{err_fmt, err_str, wutil};
use fish_util::get_seeded_rng;
use rand::rngs::SmallRng;
use rand::{Rng as _, RngCore as _};
@@ -26,7 +27,14 @@ pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
builtin_missing_argument(
parser,
streams,
cmd,
None,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -56,9 +64,7 @@ pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
let i = w.wopt_index;
if arg_count >= 1 && argv[i] == "choice" {
if arg_count == 1 {
streams
.err
.appendln(&wgettext_fmt!("%s: nothing to choose from", cmd));
err_str!("nothing to choose from").cmd(cmd).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -69,18 +75,14 @@ pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
fn parse_ll(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<i64, wutil::Error> {
let res = fish_wcstol(num);
if res.is_err() {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, num));
err_fmt!(Error::NOT_NUMBER, num).cmd(cmd).finish(streams);
}
res
}
fn parse_ull(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<u64, wutil::Error> {
let res = fish_wcstoul(num);
if res.is_err() {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, num));
err_fmt!(Error::NOT_NUMBER, num).cmd(cmd).finish(streams);
}
res
}
@@ -124,11 +126,9 @@ fn parse_ull(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<u64, wut
match parse_ull(streams, cmd, argv[i + 1]) {
Err(_) => return Err(STATUS_INVALID_ARGS),
Ok(0) => {
streams.err.appendln(&wgettext_fmt!(
"%s: %s must be a positive integer",
cmd,
"STEP"
));
err_fmt!("%s must be a positive integer", "STEP")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
Ok(x) => step = x,
@@ -140,9 +140,7 @@ fn parse_ull(streams: &mut IoStreams, cmd: &wstr, num: &wstr) -> Result<u64, wut
}
}
_ => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
err_str!(Error::TOO_MANY_ARGUMENTS).cmd(cmd).finish(streams);
return Err(STATUS_CMD_ERROR);
}
}

View File

@@ -1,6 +1,7 @@
//! Implementation of the read builtin.
use super::prelude::*;
use crate::builtins::error::Error;
use crate::common::UnescapeStringStyle;
use crate::common::bytes2wcstring;
use crate::common::escape;
@@ -11,6 +12,8 @@
use crate::env::Environment as _;
use crate::env::READ_BYTE_LIMIT;
use crate::env::{EnvVar, EnvVarFlags};
use crate::err_fmt;
use crate::err_str;
use crate::input_common::DecodeState;
use crate::input_common::InvalidPolicy;
use crate::input_common::decode_one_codepoint_utf8;
@@ -139,21 +142,17 @@ fn parse_cmd_opts(
opts.nchars = match fish_wcstoi(w.woptarg.unwrap()) {
Ok(n) if n >= 0 => NonZeroUsize::new(n.try_into().unwrap()),
Err(wutil::Error::Overflow) => {
streams.err.appendln(&wgettext_fmt!(
"%s: Argument '%s' is out of range",
cmd,
w.woptarg.unwrap()
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!("Argument '%s' is out of range", w.woptarg.unwrap())
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
_ => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_NOT_NUMBER,
cmd,
w.woptarg.unwrap()
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::NOT_NUMBER, w.woptarg.unwrap())
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -181,16 +180,17 @@ fn parse_cmd_opts(
};
if let Some(old_mode) = opts.token_mode {
if old_mode != new_mode {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
wgettext_fmt!(
"%s and %s are mutually exclusive",
tokenize_flag(old_mode),
tokenize_flag(new_mode),
)
));
builtin_print_error_trailer(parser, streams.err, cmd);
)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -209,7 +209,7 @@ fn parse_cmd_opts(
opts.split_null = true;
}
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], true);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -443,32 +443,26 @@ fn validate_read_args(
) -> BuiltinResult {
localizable_consts! {
OPTIONS_CANNOT_BE_COMBINED
"%s: Options %s and %s cannot be used together"
"Options %s and %s cannot be used together"
}
if opts.prompt.is_some() && opts.prompt_str.is_some() {
streams
.err
.appendln(&wgettext_fmt!(OPTIONS_CANNOT_BE_COMBINED, cmd, "-p", "-P",));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(OPTIONS_CANNOT_BE_COMBINED, "-p", "-P")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if opts.delimiter.is_some() && opts.one_line {
streams.err.appendln(&wgettext_fmt!(
OPTIONS_CANNOT_BE_COMBINED,
cmd,
"--delimiter",
"--line"
));
err_fmt!(OPTIONS_CANNOT_BE_COMBINED, "--delimiter", "--line")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if opts.one_line && opts.split_null {
streams.err.appendln(&wgettext_fmt!(
OPTIONS_CANNOT_BE_COMBINED,
cmd,
"-z",
"--line"
));
err_fmt!(OPTIONS_CANNOT_BE_COMBINED, "-z", "--line")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -479,10 +473,10 @@ fn validate_read_args(
}
if opts.place.mode.contains(EnvMode::UNEXPORT) && opts.place.mode.contains(EnvMode::EXPORT) {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_EXPUNEXP, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::EXPORT_UNEXPORT)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -494,17 +488,17 @@ fn validate_read_args(
.count()
> 1
{
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_GLOCAL, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::MULTIPLE_SCOPES)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if opts.array && argv.len() != 1 {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_ARG_COUNT1, cmd, 1, argv.len()));
err_fmt!(Error::UNEXP_ARG_COUNT, 1, argv.len())
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -518,22 +512,20 @@ fn tokenize_flag(token_mode: TokenOutputMode) -> &'static wstr {
if let Some(token_mode) = opts.token_mode {
if opts.delimiter.is_some() {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
err_fmt!(
Error::COMBO_EXCLUSIVE,
"--delimiter",
tokenize_flag(token_mode),
));
tokenize_flag(token_mode)
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if opts.one_line {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
"--line",
tokenize_flag(token_mode),
));
err_fmt!(Error::COMBO_EXCLUSIVE, "--line", tokenize_flag(token_mode))
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -541,17 +533,14 @@ fn tokenize_flag(token_mode: TokenOutputMode) -> &'static wstr {
// Verify all variable names.
for arg in argv {
if !valid_var_name(arg) {
streams.err.append(&varname_error(cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
varname_error(cmd, arg).full_trailer(parser).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if EnvVar::flags_for(arg).contains(EnvVarFlags::READ_ONLY) {
streams.err.append(&wgettext_fmt!(
"%s: %s: cannot overwrite read-only variable",
cmd,
arg
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!("%s: cannot overwrite read-only variable", arg)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -579,9 +568,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
// stdin may have been explicitly closed
if streams.is_stdin_closed() {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_STDIN_CLOSED, cmd));
err_str!(Error::STDIN_CLOSED).cmd(cmd).finish(streams);
return Err(STATUS_CMD_ERROR);
}

View File

@@ -4,6 +4,7 @@
use super::prelude::*;
use crate::env::Environment as _;
use crate::err_fmt;
use crate::{
path::path_apply_working_directory,
wutil::{normalize_path, wrealpath},
@@ -37,7 +38,7 @@ fn parse_options(
's' => opts.no_symlinks = true,
'h' => opts.print_help = true,
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -82,16 +83,11 @@ pub fn realpath(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
} else {
let errno = errno();
if errno.0 != 0 {
streams.err.appendln(&wgettext_fmt!(
"builtin %s: %s: %s",
cmd,
arg,
errno.to_string()
));
err_fmt!("%s: %s", arg, errno.to_string())
.cmd(cmd)
.finish(streams);
} else {
streams
.err
.appendln(&wgettext_fmt!("builtin %s: Invalid arg: %s", cmd, arg));
err_fmt!("Invalid arg: %s", arg).cmd(cmd).finish(streams);
}
had_error = true;
}
@@ -106,12 +102,9 @@ pub fn realpath(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
};
streams.out.appendln(&normalize_path(&absolute_arg, false));
} else {
streams.err.appendln(&wgettext_fmt!(
"builtin %s: %s failed: %s",
cmd,
"realpath",
errno().to_string()
));
err_fmt!("%s failed: %s", "realpath", errno().to_string())
.cmd(cmd)
.finish(streams);
had_error = true;
}
}

View File

@@ -2,6 +2,8 @@
use std::ops::ControlFlow;
use crate::{builtins::error::Error, err_fmt, err_str};
use super::prelude::*;
#[derive(Debug, Clone, Copy, Default)]
@@ -27,7 +29,7 @@ fn parse_options(
match c {
'h' => opts.print_help = true,
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], true);
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], true);
return ControlFlow::Break(STATUS_INVALID_ARGS);
}
';' => {
@@ -97,10 +99,10 @@ pub fn parse_return_value(
return ControlFlow::Break(Ok(SUCCESS));
}
if optind + 1 < args.len() {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::TOO_MANY_ARGUMENTS)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return ControlFlow::Break(Err(STATUS_INVALID_ARGS));
}
if optind == args.len() {
@@ -109,10 +111,10 @@ pub fn parse_return_value(
match fish_wcstoi(args[optind]) {
Ok(i) => ControlFlow::Continue(i),
Err(_e) => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, args[1]));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::NOT_NUMBER, args[1])
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
ControlFlow::Break(Err(STATUS_INVALID_ARGS))
}
}

View File

@@ -1,4 +1,5 @@
use super::prelude::*;
use crate::builtins::error::Error;
use crate::common::EscapeFlags;
use crate::common::EscapeStringStyle;
use crate::common::escape;
@@ -7,6 +8,8 @@
use crate::env::EnvStackSetResult;
use crate::env::EnvVarFlags;
use crate::env::INHERITED_VARS;
use crate::err_fmt;
use crate::err_str;
use crate::event;
use crate::event::Event;
use crate::expand::expand_escape_string;
@@ -158,7 +161,14 @@ fn parse(
opts.preserve_failure_exit_status = false;
}
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
builtin_missing_argument(
parser,
streams,
cmd,
None,
args[w.wopt_index - 1],
false,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -176,22 +186,24 @@ fn parse(
let optind = w.wopt_index;
// implicit drop(w); here
if args[optind - 1].starts_with("-o") {
// TODO: translate this
streams.err.appendln(&sprintf!(
"Fish does not have shell options. See `help %s`.",
let mut err = err_fmt!(
"fish does not have shell options. See `help %s`.",
help_section!("fish_for_bash_users")
));
);
if optind < args.len() {
err.append_assign_to_msg('\n');
if args[optind] == "vi" {
// Tell the vi users how to get what they need.
streams
.err
.appendln(L!("To enable vi-mode, run `fish_vi_key_bindings`."));
err.append_assign_to_msg(wgettext!(
"To enable vi-mode, run `fish_vi_key_bindings`."
));
} else if args[optind] == "ed" {
// This should be enough for make ed users feel at home
streams.err.append(L!("?\n?\n?\n"));
err.append_assign_to_msg(L!("?\n?\n?\n"));
}
}
err.finish(streams);
}
builtin_unknown_option(parser, streams, cmd, args[optind - 1], false);
@@ -225,15 +237,19 @@ fn validate(
) -> Result<(), ErrorCode> {
// Can't query and erase or list.
if opts.query && (opts.erase || opts.list) {
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
// We can't both list and erase variables.
if opts.erase && opts.list {
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -246,35 +262,37 @@ fn validate(
// ..unless we are erasing a variable, in which case we can erase from several in one go.
&& !opts.erase
{
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_GLOCAL, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::MULTIPLE_SCOPES)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
// Variables can only have one export status.
if opts.exportv && opts.unexport {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_EXPUNEXP, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::EXPORT_UNEXPORT)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
// Variables can only have one path status.
if opts.pathvar && opts.unpathvar {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_PATHUNPATH, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::PATH_UNPATH)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
// Trying to erase and (un)export at the same time doesn't make sense.
if opts.erase && (opts.exportv || opts.unexport) {
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -288,18 +306,18 @@ fn validate(
|| opts.exportv
|| opts.universal)
{
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::INVALID_OPT_COMBO)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if args.len() == optind && opts.erase {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_MISSING_OPT_ARG,
cmd,
L!("--erase")
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::MISSING_OPT_ARG, L!("--erase"))
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -320,11 +338,12 @@ fn warn_if_uvar_shadows_global(
&& parser.is_interactive()
&& parser.vars().getf(dest, EnvMode::GLOBAL).is_some()
{
streams.err.appendln(&wgettext_fmt!(
"%s: successfully set universal '%s'; but a global by that name shadows it",
cmd,
err_fmt!(
"successfully set universal '%s'; but a global by that name shadows it",
dest
));
)
.cmd(cmd)
.finish(streams);
}
}
@@ -332,25 +351,25 @@ fn handle_env_return(retval: EnvStackSetResult, cmd: &wstr, key: &wstr, streams:
match retval {
EnvStackSetResult::Ok => (),
EnvStackSetResult::Perm => {
streams.err.appendln(&wgettext_fmt!(
"%s: Tried to change the read-only variable '%s'",
cmd,
key
));
err_fmt!("Tried to change the read-only variable '%s'", key)
.cmd(cmd)
.finish(streams);
}
EnvStackSetResult::Scope => {
streams.err.appendln(&wgettext_fmt!(
"%s: Tried to modify the special variable '%s' with the wrong scope",
cmd,
err_fmt!(
"Tried to modify the special variable '%s' with the wrong scope",
key
));
)
.cmd(cmd)
.finish(streams);
}
EnvStackSetResult::Invalid => {
streams.err.appendln(&wgettext_fmt!(
"%s: Tried to modify the special variable '%s' to an invalid value",
cmd,
err_fmt!(
"Tried to modify the special variable '%s' to an invalid value",
key
));
)
.cmd(cmd)
.finish(streams);
}
EnvStackSetResult::NotFound => {
// Only variable deletion can return a `NotFound` error, but that case is explicitly silenced
@@ -393,7 +412,9 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
"{}\n",
match self {
EnvArrayParseError::InvalidIndex(varname) =>
wgettext_fmt!("%s: Invalid index starting at '%s'", "set", varname).to_string(),
err_fmt!("Invalid index starting at '%s'", varname)
.cmd(L!("set"))
.to_string(),
}
)
}
@@ -736,17 +757,15 @@ fn show(cmd: &wstr, parser: &Parser, streams: &mut IoStreams, args: &[&wstr]) ->
};
if !valid_var_name(arg) {
streams.err.append(&varname_error(cmd, arg));
builtin_print_error_trailer(parser, streams.err, cmd);
varname_error(cmd, arg).full_trailer(parser).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if bracket.is_some() {
streams.err.appendln(&wgettext_fmt!(
"%s: `set --show` does not allow slices with the var names",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("`set --show` does not allow slices with the var names")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
@@ -784,8 +803,9 @@ fn erase(
};
if !valid_var_name(split.varname) {
streams.err.append(&varname_error(cmd, split.varname));
builtin_print_error_trailer(parser, streams.err, cmd);
varname_error(cmd, split.varname)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let retval;
@@ -945,10 +965,10 @@ fn set_internal(
argv: &[&wstr],
) -> BuiltinResult {
if argv.is_empty() {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_MIN_ARG_COUNT1, cmd, 1, 0));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::MIN_ARG_COUNT, 1, 0)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -963,16 +983,19 @@ fn set_internal(
// Is the variable valid?
if !valid_var_name(split.varname) {
streams.err.append(&varname_error(cmd, split.varname));
let mut err = varname_error(cmd, split.varname);
if let Some(pos) = split.varname.chars().position(|c| c == '=') {
streams.err.append(&wgettext_fmt!(
"%s: Did you mean `set %s %s`?",
cmd,
err.append_assign_to_msg('\n');
let extra = err_fmt!(
"Did you mean `set %s %s`?",
&escape(&split.varname[..pos]),
&escape(&split.varname[pos + 1..])
));
)
.cmd(cmd)
.to_string();
err.append_assign_to_msg(&extra);
}
builtin_print_error_trailer(parser, streams.err, cmd);
err.full_trailer(parser).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -981,31 +1004,31 @@ fn set_internal(
// Indexes must be > 0. (Note split_var_and_indexes negates negative values).
for ind in &split.indexes {
if *ind <= 0 {
streams
.err
.appendln(&wgettext_fmt!("%s: array index out of bounds", cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("array index out of bounds")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
// Append and prepend are disallowed.
if opts.append || opts.prepend {
streams.err.append(&wgettext_fmt!(
"%s: Cannot use --append or --prepend when assigning to a slice",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("Cannot use --append or --prepend when assigning to a slice")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
// Argument count and index count must agree.
if split.indexes.len() != argv.len() {
streams.err.appendln(&wgettext_fmt!(
"%s: given %d indexes but %d values",
cmd,
err_fmt!(
"given %d indexes but %d values",
split.indexes.len(),
argv.len()
));
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}

View File

@@ -1,7 +1,9 @@
// Implementation of the set_color builtin.
use super::prelude::*;
use crate::builtins::error::Error;
use crate::common::bytes2wcstring;
use crate::err_fmt;
use crate::screen::{is_dumb, only_grayscale};
use crate::terminal::Outputter;
use crate::text_face::{self, PrintColorsArgs, TextFace, TextStyling, parse_text_face_and_options};
@@ -76,70 +78,40 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
builtin_print_help(parser, streams, argv[0]);
return Ok(SUCCESS);
}
Err(MissingOptArg) => {
// Either "--background" or "--underline-color" are missing an argument.
// Don't print an error, for consistency with "set_color".
// 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(InvalidOptArg(name, value)) => {
streams.err.appendln(&wgettext_fmt!(
"%s: %s: invalid option argument: %s",
argv[0],
name,
value
));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownColor(arg)) => {
streams
.err
.appendln(&wgettext_fmt!("%s: Unknown color '%s'", argv[0], arg));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownUnderlineStyle(arg)) => {
streams.err.appendln(&wgettext_fmt!(
"%s: invalid underline style: %s",
argv[0],
arg
));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownOption(unknown_option_index)) => {
builtin_unknown_option(
parser,
streams,
L!("set_color"),
argv[unknown_option_index],
true, /* print_hints */
);
return Err(STATUS_INVALID_ARGS);
}
Err(InvalidFgArgCombination) => {
streams.err.appendln(&wgettext_fmt!(
"%s: %s: option cannot be used with a non-option argument",
argv[0],
"--foreground",
));
return Err(STATUS_INVALID_ARGS);
}
Err(InvalidFgPrintColorCombination) => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
argv[0],
"--foreground",
"--print-colors",
));
Err(err) => {
let error = match err {
MissingOptArg => {
// Either "--background" or "--underline-color" are missing an argument.
// Don't print an error, for consistency with "set_color".
// In future we change both to actually print an error.
return Err(STATUS_INVALID_ARGS);
}
UnexpectedOptArg(option_index) => {
err_fmt!(Error::UNEXP_OPT_ARG, argv[option_index]).full_trailer(parser)
}
InvalidOptArg(name, value) => {
err_fmt!("%s: invalid option argument: %s", name, value)
}
UnknownColor(arg) => {
err_fmt!("Unknown color '%s'", arg)
}
UnknownUnderlineStyle(arg) => {
err_fmt!("invalid underline style: %s", arg)
}
UnknownOption(unknown_option_index) => {
err_fmt!(Error::UNKNOWN_OPT, argv[unknown_option_index]).full_trailer(parser)
}
InvalidFgArgCombination => {
err_fmt!(
"%s: option cannot be used with a non-option argument",
"--foreground"
)
}
InvalidFgPrintColorCombination => {
err_fmt!(Error::COMBO_EXCLUSIVE, "--foreground", "--print-colors",)
}
};
error.cmd(argv[0]).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
};

View File

@@ -0,0 +1,261 @@
use std::borrow::Cow;
use crate::{
io::{IoStreams, OutputStream, StringOutputStream},
parser::Parser,
prelude::*,
wgettext_fmt,
};
use fish_widestring::wstr;
#[macro_export]
macro_rules! err_fmt {
(
$string:expr // format string (literal or LocalizableString)
$(, $args:expr)* // list of expressions
$(,)? // optional trailing comma
) => {
$crate::builtins::error::Error::new(
$crate::wgettext_fmt!($string, $($args),*).into()
)
};
}
pub use err_fmt;
#[macro_export]
macro_rules! err_str {
(
$string:expr // format string (literal or LocalizableString)
$(,)? // optional trailing comma
) => {
$crate::builtins::error::Error::new(std::borrow::Cow::Borrowed($crate::wgettext!($string)))
};
}
pub use err_str;
/// Generate an `Error` from a string without localization. This is typically
/// for error messages from external sources (e.g. C `strerror()`)
#[macro_export]
macro_rules! err_raw {
(
$string:expr // owned WString
) => {
$crate::builtins::error::Error::new($string.into())
};
}
pub use err_raw;
pub struct Error<'a> {
msg: Cow<'a, wstr>,
cmd: Option<&'a wstr>,
subcmd: Option<&'a wstr>,
parser: Option<&'a Parser>,
hint: bool,
}
impl<'a> Error<'a> {
localizable_consts!(
/// Error message on missing argument.
pub MISSING_OPT_ARG
"%s: option requires an argument"
/// Error message on unexpected argument.
pub UNEXP_OPT_ARG
"%s: option does not take an argument"
/// Error message on missing man page.
pub MISSING_HELP
"missing man page\nDocumentation may not be installed.\n`help %s` will show an online version"
/// Error message on multiple scope levels for variables.
pub MULTIPLE_SCOPES
"scope can be only one of: universal function global local"
/// Error message for specifying both export and unexport to set/read.
pub EXPORT_UNEXPORT
"cannot both export and unexport"
/// Error message for specifying both path and unpath to set/read.
pub PATH_UNPATH
"cannot both path and unpath"
/// Error message for unknown switch.
pub UNKNOWN_OPT
"%s: unknown option"
/// Error message for invalid bind mode name.
pub BIND_MODE
"%s: invalid mode name. See `help %s`"
/// Error message when too many arguments are supplied to a builtin.
pub TOO_MANY_ARGUMENTS
"too many arguments"
/// Error message when integer expected
pub NOT_NUMBER
"%s: invalid integer"
/// Command that requires a subcommand was invoked without a recognized subcommand.
pub MISSING_SUBCMD
"missing subcommand"
pub INVALID_SUBCMD
"invalid subcommand"
pub INVALID_SUBSUBCMD
"%s: invalid subcommand"
/// Error messages for unexpected args.
pub MISSING_ARG
"missing argument"
pub UNEXP_ARG_COUNT
"expected %d arguments; got %d"
pub UNPEXP_ARG_COUNT_WITH_CTX
"%s: expected %d arguments; got %d"
pub MIN_ARG_COUNT
"expected >= %d arguments; got %d"
pub MAX_ARG_COUNT
"expected <= %d arguments; got %d"
/// Error message for invalid variable name.
pub INVALID_VARNAME
"%s: invalid variable name. See `help %s`"
/// Error message on invalid combination of options.
pub INVALID_OPT_COMBO
"invalid option combination"
pub INVALID_OPT_COMBO_WITH_CTX
"invalid option combination, %s"
pub COMBO_EXCLUSIVE
"%s %s: options cannot be used together"
pub REGEX_COMPILE
"Regular expression compile error: %s"
pub NO_SUITABLE_JOBS
"There are no suitable jobs"
pub COULD_NOT_FIND_JOB
"Could not find job '%d'"
pub STDIN_CLOSED
"stdin is closed"
pub INVALID_MAX_MATCHES
"Invalid max matches value '%s'"
pub INVALID_MAX_VALUE
"Invalid max value '%s'"
);
#[must_use]
pub fn new(msg: Cow<'a, wstr>) -> Self {
Error {
msg,
cmd: Default::default(),
subcmd: Default::default(),
parser: Default::default(),
hint: Default::default(),
}
}
#[must_use]
pub fn cmd(mut self, cmd: &'a wstr) -> Self {
self.cmd = Some(cmd);
self
}
#[must_use]
pub fn subcmd(mut self, cmd: &'a wstr, subcmd: &'a wstr) -> Self {
self.cmd = Some(cmd);
self.subcmd = Some(subcmd);
self
}
#[must_use]
pub fn stacktrace(mut self, parser: &'a Parser) -> Self {
self.parser = Some(parser);
self
}
#[must_use]
pub fn hint(mut self) -> Self {
self.hint = true;
self
}
// Convenience function for both stacktrace and hint
#[must_use]
pub fn full_trailer(self, parser: &'a Parser) -> Self {
self.stacktrace(parser).hint()
}
#[must_use]
pub fn append_to_msg(mut self, append: impl IntoCharIter) -> Self {
self.append_assign_to_msg(append);
self
}
pub fn append_assign_to_msg(&mut self, append: impl IntoCharIter) {
let s = self.msg.to_mut();
if !append.extend_wstring(s) {
s.extend(append.chars());
}
}
pub fn finish(self, streams: &mut IoStreams) {
self.write_to(streams.err);
}
pub fn to_string(&self) -> WString {
let mut out = OutputStream::String(StringOutputStream::new());
self.write_to(&mut out);
out.take()
}
pub fn write_to(&self, output: &mut OutputStream) {
self.write_msg(output);
self.write_stacktrace(output);
self.write_hint(output);
}
fn write_msg(&self, output: &mut OutputStream) {
let str: &wstr = match (self.cmd, self.subcmd) {
(None, _) => &self.msg,
(Some(cmd), None) => &wgettext_fmt!("%s: %s", cmd, &self.msg),
(Some(cmd), Some(subcmd)) => &wgettext_fmt!("%s %s: %s", cmd, subcmd, &self.msg),
};
output.appendln(str);
}
fn write_stacktrace(&self, output: &mut OutputStream) {
let Some(parser) = self.parser else {
return;
};
let stacktrace = parser.current_line();
if !stacktrace.is_empty() {
output.append('\n');
output.appendln(&stacktrace);
}
}
fn write_hint(&self, output: &mut OutputStream) {
if !self.hint {
return;
}
let Some(cmd) = self.cmd else {
return;
};
output.appendln(&wgettext_fmt!(
"(Type 'help %s' for related documentation)",
cmd
));
}
}

View File

@@ -1,4 +1,3 @@
use super::prelude::*;
use crate::common::{Named, bytes2wcstring, escape, get_by_sorted_name, str2wcstring};
use crate::fds::BorrowedFdFile;
use crate::io::OutputStream;
@@ -6,7 +5,7 @@
use crate::parse_util::argument_is_help;
use crate::parser::{BlockType, LoopStatus};
use crate::proc::{Pid, ProcStatus, no_exec};
use crate::{builtins::*, wutil};
use crate::{builtins::prelude::*, builtins::*, err_fmt, wutil};
use errno::errno;
use fish_common::assert_sorted_by_name;
use fish_widestring::L;
@@ -19,104 +18,6 @@
L!("set_color green; echo -n read; set_color --reset; echo -n \"> \"");
localizable_consts!(
/// Error message on missing argument.
pub BUILTIN_ERR_MISSING_OPT_ARG
"%s: %s: option requires an argument"
/// Error message on unexpected argument.
pub BUILTIN_ERR_UNEXP_OPT_ARG
"%s: %s: option does not take an argument"
/// Error message on missing man page.
pub BUILTIN_ERR_MISSING_HELP
"fish: %s: missing man page\nDocumentation may not be installed.\n`help %s` will show an online version"
/// Error message on multiple scope levels for variables.
pub BUILTIN_ERR_GLOCAL
"%s: scope can be only one of: universal function global local"
/// Error message for specifying both export and unexport to set/read.
pub BUILTIN_ERR_EXPUNEXP
"%s: cannot both export and unexport"
/// Error message for specifying both path and unpath to set/read.
pub BUILTIN_ERR_PATHUNPATH
"%s: cannot both path and unpath"
/// Error message for unknown switch.
pub BUILTIN_ERR_UNKNOWN_OPT
"%s: %s: unknown option"
/// Error message for invalid bind mode name.
pub BUILTIN_ERR_BIND_MODE
"%s: %s: invalid mode name. See `help %s`"
/// Error message when too many arguments are supplied to a builtin.
pub BUILTIN_ERR_TOO_MANY_ARGUMENTS
"%s: too many arguments"
/// Error message when integer expected
pub BUILTIN_ERR_NOT_NUMBER
"%s: %s: invalid integer"
/// Command that requires a subcommand was invoked without a recognized subcommand.
pub BUILTIN_ERR_MISSING_SUBCMD
"%s: missing subcommand"
pub BUILTIN_ERR_INVALID_SUBCMD
"%s: %s: invalid subcommand"
pub BUILTIN_ERR_INVALID_SUBSUBCMD
"%s %s: %s: invalid subcommand"
/// Error messages for unexpected args.
pub BUILTIN_ERR_ARG_COUNT0
"%s: missing argument"
pub BUILTIN_ERR_ARG_COUNT1
"%s: expected %d arguments; got %d"
pub BUILTIN_ERR_ARG_COUNT2
"%s: %s: expected %d arguments; got %d"
pub BUILTIN_ERR_MIN_ARG_COUNT1
"%s: expected >= %d arguments; got %d"
pub BUILTIN_ERR_MAX_ARG_COUNT1
"%s: expected <= %d arguments; got %d"
/// Error message for invalid variable name.
pub BUILTIN_ERR_VARNAME
"%s: %s: invalid variable name. See `help %s`"
/// Error message on invalid combination of options.
pub BUILTIN_ERR_COMBO
"%s: invalid option combination"
pub BUILTIN_ERR_COMBO2
"%s: invalid option combination, %s"
pub BUILTIN_ERR_COMBO2_EXCLUSIVE
"%s: %s %s: options cannot be used together"
pub BUILTIN_ERR_REGEX_COMPILE
"%s: Regular expression compile error: %s"
pub BUILTIN_ERR_NO_SUITABLE_JOBS
"%s: There are no suitable jobs"
pub BUILTIN_ERR_COULD_NOT_FIND_JOB
"%s: Could not find job '%d'"
pub BUILTIN_ERR_STDIN_CLOSED
"%s: stdin is closed"
pub BUILTIN_ERR_INVALID_MAX_MATCHES
"%s: Invalid max matches value '%s'"
pub BUILTIN_ERR_INVALID_MAX_VALUE
"%s: Invalid max value '%s'"
/// The send stuff to foreground message.
pub FG_MSG
"Send job %d (%s) to foreground"
@@ -657,9 +558,9 @@ pub fn builtin_print_help(parser: &Parser, streams: &mut IoStreams, cmd: &wstr)
false,
);
if res.status.normal_exited() && res.status.exit_code() == 2 {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING_HELP, name_esc, name_esc));
err_fmt!(Error::MISSING_HELP, name_esc)
.cmd(&name_esc)
.finish(streams);
}
}
@@ -671,38 +572,38 @@ pub fn builtin_unknown_option(
opt: &wstr,
print_hints: bool, /*=true*/
) {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_UNKNOWN_OPT, cmd, opt));
let mut err = err_fmt!(Error::UNKNOWN_OPT, opt).cmd(cmd);
if print_hints {
builtin_print_error_trailer(parser, streams.err, cmd);
err = err.full_trailer(parser);
}
err.finish(streams);
}
/// Perform error reporting for encounter with missing argument.
/// Perform error reporting for encounter with missing argument for subcommands.
pub fn builtin_missing_argument(
parser: &Parser,
streams: &mut IoStreams,
cmd: &wstr,
subcmd: Option<&wstr>,
mut opt: &wstr,
print_hints: bool, /*=true*/
) {
if opt.char_at(0) == '-' && opt.char_at(1) != '-' {
let mut err = if opt.char_at(0) == '-' && opt.char_at(1) != '-' {
// if c in -qc '-qc' is missing the argument, now opt is just 'c'
opt = &opt[opt.len() - 1..];
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_MISSING_OPT_ARG,
cmd,
L!("-").to_owned() + opt
));
err_fmt!(Error::MISSING_OPT_ARG, L!("-").to_owned() + opt)
} else {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING_OPT_ARG, cmd, opt));
err_fmt!(Error::MISSING_OPT_ARG, opt)
};
if let Some(subcmd) = subcmd {
err = err.subcmd(cmd, subcmd);
} else {
err = err.cmd(cmd);
}
if print_hints {
builtin_print_error_trailer(parser, streams.err, cmd);
err = err.full_trailer(parser);
}
err.finish(streams);
}
/// Perform error reporting for encounter with an extra argument.
@@ -713,12 +614,11 @@ pub fn builtin_unexpected_argument(
opt: &wstr,
print_hints: bool, /*=true*/
) {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_UNEXP_OPT_ARG, cmd, opt));
let mut err = err_fmt!(Error::UNEXP_OPT_ARG, opt).cmd(cmd);
if print_hints {
builtin_print_error_trailer(parser, streams.err, cmd);
err = err.full_trailer(parser);
}
err.finish(streams);
}
/// Print the backtrace and call for help that we use at the end of error messages.
@@ -735,16 +635,8 @@ pub fn builtin_print_error_trailer(parser: &Parser, b: &mut OutputStream, cmd: &
));
}
/// This function works like perror, but it prints its result into the streams.err string instead
/// to stderr. Used by the builtin commands.
pub fn builtin_wperror(program_name: &wstr, streams: &mut IoStreams) {
let err = errno();
streams.err.append(program_name);
streams.err.append(L!(": "));
if err.0 != 0 {
let werr = str2wcstring(err.to_string());
streams.err.appendln(&werr);
}
pub fn builtin_strerror() -> WString {
str2wcstring(errno().to_string())
}
pub struct HelpOnlyCmdOpts {
@@ -776,6 +668,7 @@ pub fn parse(
parser,
streams,
cmd,
None,
args[w.wopt_index - 1],
print_hints,
);
@@ -997,11 +890,9 @@ fn parsed_pid(
match pid {
Ok(pid @ 1..) => Ok(Pid::new(pid)),
_ => {
streams.err.appendln(&wgettext_fmt!(
"%s: '%s' is not a valid process ID",
cmd,
arg
));
err_fmt!("'%s' is not a valid process ID", arg)
.cmd(cmd)
.finish(streams);
Err(STATUS_INVALID_ARGS)
}
}
@@ -1046,9 +937,9 @@ pub fn builtin_break_continue(
}
if argc != 1 {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_UNKNOWN_OPT, argv[0], argv[1]));
err_fmt!(Error::UNKNOWN_OPT, argv[1])
.cmd(argv[0])
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -1118,11 +1009,13 @@ pub fn parse_from_opt(
arg: &wstr,
) -> Result<Self, ErrorCode> {
Self::try_from(arg).map_err(|()| {
streams.err.appendln(&wgettext_fmt!(
"%s: Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'",
cmd,
err_fmt!(
"Invalid value for '--color' option: '%s'. Expected 'always', 'never', or 'auto'",
arg
));
)
.cmd(cmd)
.finish(streams);
STATUS_INVALID_ARGS
})
}

View File

@@ -0,0 +1,5 @@
pub mod error;
pub mod misc;
pub use error::*;
pub use misc::*;

View File

@@ -1,7 +1,9 @@
use std::os::fd::AsRawFd as _;
use crate::{
builtins::error::Error,
common::{FilenameRef, escape},
err_fmt, err_raw, err_str,
fds::wopen_cloexec,
nix::isatty,
parser::Block,
@@ -37,18 +39,15 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
if argc == optind || args[optind] == "-" {
if streams.is_stdin_closed() {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_STDIN_CLOSED, cmd));
err_str!(Error::STDIN_CLOSED).cmd(cmd).finish(streams);
return Err(STATUS_CMD_ERROR);
}
// Either a bare `source` which means to implicitly read from stdin or an explicit `-`.
if argc == optind && isatty(streams.stdin_fd()) {
// Don't implicitly read from the terminal.
streams.err.appendln(&wgettext_fmt!(
"%s: missing filename argument or input redirection",
cmd
));
err_str!("missing filename argument or input redirection")
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
func_filename = FilenameRef::new(L!("-").to_owned());
@@ -60,12 +59,12 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
Err(_) => {
let esc = escape(args[optind]);
streams.err.appendln(&wgettext_fmt!(
"%s: Error encountered while sourcing file '%s':",
cmd,
&esc
));
builtin_wperror(cmd, streams);
err_fmt!("Error encountered while sourcing file '%s':", &esc)
.append_to_msg('\n')
.append_to_msg(&err_raw!(&builtin_strerror()).cmd(cmd).to_string())
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
}
}
@@ -97,11 +96,12 @@ pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
Ok(_) => BuiltinResult::from_dynamic(parser.get_last_status()),
Err(err) => {
let esc = escape(&func_filename);
streams.err.appendln(&wgettext_fmt!(
"%s: Error while reading file '%s'",
cmd,
err_fmt!(
"Error while reading file '%s'",
if esc == "-" { L!("<stdin>") } else { &esc }
));
)
.cmd(cmd)
.finish(streams);
Err(err)
}
}

View File

@@ -1,6 +1,10 @@
use super::prelude::*;
use crate::builtins::error;
use crate::common::{bytes2wcstring, get_program_name, osstr2wcstring, str2wcstring};
use crate::env::config_paths::get_fish_path;
use crate::err_fmt;
#[cfg(not(feature = "localize-messages"))]
use crate::err_raw;
use crate::proc::{
JobControl, get_job_control_mode, get_login, is_interactive_session, set_job_control_mode,
};
@@ -105,12 +109,13 @@ impl StatusCmdOpts {
fn try_set_status_cmd(&mut self, subcmd: StatusCmd, streams: &mut IoStreams) -> bool {
match self.status_cmd.replace(subcmd) {
Some(existing) => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
"status",
err_fmt!(
error::Error::COMBO_EXCLUSIVE,
existing.to_wstr(),
subcmd.to_wstr(),
));
)
.cmd(L!("status"))
.finish(streams);
false
}
None => true,
@@ -171,7 +176,7 @@ fn default() -> Self {
];
localizable_consts! {
BUILTIN_INVALID_JOB_CONTROL_MODE "%s: Invalid job control mode '%s'"
BUILTIN_INVALID_JOB_CONTROL_MODE "Invalid job control mode '%s'"
}
/// Print the features and their values.
@@ -215,17 +220,15 @@ fn parse_cmd_opts(
match fish_wcstoi(arg) {
Ok(level) if level >= 0 => level,
Err(Error::Overflow) | Ok(_) => {
streams.err.appendln(&wgettext_fmt!(
"%s: Invalid level value '%s'",
cmd,
arg
));
err_fmt!("Invalid level value '%s'", arg)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
_ => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, arg));
err_fmt!(error::Error::NOT_NUMBER, arg)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -251,11 +254,9 @@ fn parse_cmd_opts(
return Err(STATUS_CMD_ERROR);
}
let Ok(job_mode) = w.woptarg.unwrap().try_into() else {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_INVALID_JOB_CONTROL_MODE,
cmd,
w.woptarg.unwrap()
));
err_fmt!(BUILTIN_INVALID_JOB_CONTROL_MODE, w.woptarg.unwrap())
.cmd(cmd)
.finish(streams);
return Err(STATUS_CMD_ERROR);
};
opts.new_job_control_mode = Some(job_mode);
@@ -287,7 +288,7 @@ fn parse_cmd_opts(
}
'h' => opts.print_help = true,
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
builtin_missing_argument(parser, streams, cmd, None, args[w.wopt_index - 1], false);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -363,9 +364,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
optind += 1;
}
None => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_INVALID_SUBCMD, cmd, args[1]));
err_fmt!(error::Error::INVALID_SUBCMD)
.subcmd(cmd, args[1])
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -406,21 +407,15 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
None => {
if args.len() != 1 {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT2,
cmd,
c.to_wstr(),
1,
args.len()
));
err_fmt!(error::Error::UNEXP_ARG_COUNT, 1, args.len())
.subcmd(cmd, c.to_wstr())
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let Ok(new_mode) = args[0].try_into() else {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_INVALID_JOB_CONTROL_MODE,
cmd,
args[0]
));
err_fmt!(BUILTIN_INVALID_JOB_CONTROL_MODE, args[0])
.subcmd(cmd, c.to_wstr())
.finish(streams);
return Err(STATUS_CMD_ERROR);
};
new_mode
@@ -431,13 +426,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
STATUS_FEATURES => print_features(streams),
c @ STATUS_TEST_FEATURE => {
if args.len() != 1 {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT2,
cmd,
c.to_wstr(),
1,
args.len()
));
err_fmt!(error::Error::UNEXP_ARG_COUNT, 1, args.len())
.subcmd(cmd, c.to_wstr())
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let mut retval = TestFeatureRetVal::TEST_FEATURE_NOT_RECOGNIZED;
@@ -453,13 +444,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
c @ STATUS_GET_FILE => {
if args.len() != 1 {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT2,
cmd,
c.to_wstr(),
1,
args.len()
));
err_fmt!(error::Error::UNEXP_ARG_COUNT, 1, args.len())
.subcmd(cmd, c.to_wstr())
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
let arg = wcs2bytes(args[0]);
@@ -477,7 +464,8 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
STATUS_LANGUAGE => {
cfg_if! {
if #[cfg(not(feature = "localize-messages"))] {
streams.err.append(L!("fish was built with the `localize-messages` feature disabled. The `status language` command is unavailable.\n"));
err_raw!(L!("fish was built with the `localize-messages` feature disabled. The `status language` command is unavailable.").to_owned())
.finish(streams);
return Err(STATUS_CMD_ERROR);
} else {
if args.is_empty() {
@@ -506,9 +494,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
return Ok(SUCCESS);
}
invalid => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_INVALID_SUBSUBCMD, cmd, subcmd.to_wstr(), invalid));
err_fmt!(error::Error::INVALID_SUBSUBCMD, invalid)
.subcmd(cmd, subcmd.to_wstr())
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -550,22 +538,15 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
c @ STATUS_TEST_TERMINAL_FEATURE => {
if args.len() != 1 {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT2,
cmd,
c.to_wstr(),
1,
args.len()
));
err_fmt!(error::Error::UNEXP_ARG_COUNT, 1, args.len())
.subcmd(cmd, c.to_wstr())
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if args[0] != "scroll-content-up" {
streams.err.appendln(&wgettext_fmt!(
"%s %s: unrecognized feature '%s'",
cmd,
c.to_wstr(),
args[0]
));
err_fmt!("unrecognized feature '%s'", args[0])
.subcmd(cmd, c.to_wstr())
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
return if get_scroll_content_up_capability() == Some(true) {
@@ -577,13 +558,9 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
ref s => {
if !args.is_empty() {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT2,
cmd,
s.to_wstr(),
0,
args.len()
));
err_fmt!(error::Error::UNEXP_ARG_COUNT, 0, args.len())
.subcmd(cmd, s.to_wstr())
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
match s {

View File

@@ -1,4 +1,4 @@
use crate::screen::escape_code_length;
use crate::{builtins::error::Error, err_fmt, err_raw, err_str, screen::escape_code_length};
use fish_wcstringutil::fish_wcwidth_visible;
// Forward some imports to make subcmd implementations easier
use super::prelude::*;
@@ -21,30 +21,12 @@
#[cfg(test)]
mod test_helpers;
macro_rules! string_error {
(
$streams:expr,
$string:expr
$(, $args:expr)+
$(,)?
) => {
$streams.err.append(L!("string "));
$streams.err.appendln(&wgettext_fmt!($string, $($args),*));
};
}
use string_error;
trait StringSubCommand<'args> {
const SHORT_OPTIONS: &'static wstr;
const LONG_OPTIONS: &'static [WOption<'static>];
/// Parse and store option specified by the associated short or long option.
fn parse_opt(
&mut self,
name: &wstr,
c: char,
arg: Option<&'args wstr>,
) -> Result<(), StringError>;
fn parse_opt(&mut self, c: char, arg: Option<&'args wstr>) -> Result<(), StringError<'_>>;
fn parse_opts(
&mut self,
@@ -52,7 +34,8 @@ fn parse_opts(
parser: &Parser,
streams: &mut IoStreams,
) -> Result<usize, ErrorCode> {
let cmd = args[0];
let cmd = L!("string");
let subcmd = args[0];
let mut args_read = Vec::with_capacity(args.len());
args_read.extend_from_slice(args);
@@ -60,42 +43,34 @@ fn parse_opts(
while let Some(c) = w.next_opt() {
match c {
':' => {
streams.err.append(L!("string ")); // clone of string_error
builtin_missing_argument(
parser,
streams,
cmd,
Some(subcmd),
args_read[w.wopt_index - 1],
false,
);
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,
);
err_fmt!(Error::UNEXP_OPT_ARG, args_read[w.wopt_index - 1])
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
string_error!(
streams,
BUILTIN_ERR_UNKNOWN_OPT,
cmd,
args_read[w.wopt_index - 1]
);
builtin_print_error_trailer(parser, streams.err, L!("string"));
err_fmt!(Error::UNKNOWN_OPT, args_read[w.wopt_index - 1])
.subcmd(cmd, subcmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
c => {
let retval = self.parse_opt(cmd, c, w.woptarg);
let retval = self.parse_opt(c, w.woptarg);
if let Err(e) = retval {
e.print_error(&args_read, streams, w.woptarg, w.wopt_index);
return Err(e.retval());
return Err(STATUS_INVALID_ARGS);
}
}
}
@@ -155,7 +130,9 @@ fn run_impl(
self.take_args(&mut optind, args, streams)?;
if streams.stdin_is_directly_redirected && args.len() > optind {
string_error!(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, args[0]);
err_str!(Error::TOO_MANY_ARGUMENTS)
.subcmd(L!("string"), args[0])
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -164,8 +141,8 @@ fn run_impl(
}
/// This covers failing argument/option parsing
enum StringError {
InvalidArgs(WString),
enum StringError<'a> {
InvalidArgs(Error<'a>),
NotANumber,
UnknownOption,
}
@@ -178,77 +155,72 @@ enum RegexError {
impl RegexError {
fn print_error(&self, args: &[&wstr], streams: &mut IoStreams) {
let cmd = args[0];
let cmd = L!("string");
let subcmd = args[0];
use RegexError::*;
match self {
Compile(pattern, e) => {
string_error!(
streams,
BUILTIN_ERR_REGEX_COMPILE,
cmd,
&WString::from(e.error_message())
);
string_error!(streams, "%s: %s", cmd, pattern);
// TODO: This is misaligned if `pattern` contains characters which are not exactly 1
// terminal cell wide.
let mut marker = " ".repeat(e.offset().unwrap_or(0).saturating_sub(1));
let mut marker: WString =
" ".repeat(e.offset().unwrap_or(0).saturating_sub(1)).into();
marker.push('^');
string_error!(streams, "%s: %s", cmd, marker);
err_fmt!(Error::REGEX_COMPILE, e.error_message())
.append_to_msg('\n')
.append_to_msg(&err_raw!(pattern).subcmd(cmd, subcmd).to_string())
.append_to_msg('\n')
.append_to_msg(&err_raw!(marker).subcmd(cmd, subcmd).to_string())
.subcmd(cmd, subcmd)
.finish(streams);
}
InvalidCaptureGroupName(name) => {
streams.err.appendln(&wgettext_fmt!(
err_fmt!(
"Modification of read-only variable \"%s\" is not allowed",
name
));
)
.finish(streams);
}
InvalidEscape(pattern) => {
string_error!(
streams,
"%s",
sprintf!(
"%s: Invalid escape sequence in pattern \"%s\"",
cmd,
pattern
)
);
err_fmt!("Invalid escape sequence in pattern \"%s\"", pattern)
.subcmd(cmd, subcmd)
.finish(streams);
}
}
}
}
impl From<crate::wutil::wcstoi::Error> for StringError {
impl<'a> From<crate::wutil::wcstoi::Error> for StringError<'a> {
fn from(_: crate::wutil::wcstoi::Error) -> Self {
StringError::NotANumber
}
}
macro_rules! invalid_args {
($msg:expr, $name:expr, $arg:expr) => {
StringError::InvalidArgs(crate::localization::wgettext_fmt!(
$msg,
$name,
$arg.unwrap()
))
};
impl<'a> From<crate::builtins::error::Error<'a>> for StringError<'a> {
fn from(error: crate::builtins::error::Error<'a>) -> Self {
StringError::InvalidArgs(error)
}
}
use invalid_args;
impl StringError {
impl<'a> StringError<'a> {
fn print_error(
&self,
self,
args: &[&wstr],
streams: &mut IoStreams,
optarg: Option<&wstr>,
optind: usize,
) {
let cmd = L!("string");
let subcmd = args[0];
use StringError::*;
match self {
InvalidArgs(msg) => {
streams.err.appendln("string ".chars().chain(msg.chars()));
InvalidArgs(err) => {
err.subcmd(cmd, subcmd).finish(streams);
}
NotANumber => {
string_error!(streams, BUILTIN_ERR_NOT_NUMBER, subcmd, optarg.unwrap());
err_fmt!(Error::NOT_NUMBER, optarg.unwrap())
.subcmd(cmd, subcmd)
.finish(streams);
}
UnknownOption => {
// This would mean the subcmd's XXX_OPTIONS does not match
@@ -260,10 +232,6 @@ fn print_error(
}
}
}
fn retval(&self) -> ErrorCode {
STATUS_INVALID_ARGS
}
}
#[derive(Default, PartialEq, Clone, Copy)]
@@ -322,10 +290,10 @@ pub fn string(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
let argc = args.len();
if argc <= 1 {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_MISSING_SUBCMD, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::MISSING_SUBCMD)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -371,10 +339,10 @@ pub fn string(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
.run(parser, streams, args),
_ => {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_INVALID_SUBCMD, cmd, subcmd_name));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(Error::INVALID_SUBCMD)
.subcmd(cmd, subcmd_name)
.full_trailer(parser)
.finish(streams);
Err(STATUS_INVALID_ARGS)
}
}

View File

@@ -13,7 +13,7 @@ impl StringSubCommand<'_> for Collect {
];
const SHORT_OPTIONS: &'static wstr = L!("Na");
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'a' => self.allow_empty = true,
'N' => self.no_trim_newlines = true,

View File

@@ -14,14 +14,14 @@ impl StringSubCommand<'_> for Escape {
];
const SHORT_OPTIONS: &'static wstr = L!("n");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'n' => self.no_quoted = true,
NON_OPTION_CHAR => {
self.style = arg
.unwrap()
.try_into()
.map_err(|_| invalid_args!("%s: Invalid escape style '%s'", name, arg))?;
.map_err(|_| err_fmt!("Invalid escape style '%s'", arg.unwrap()))?;
}
_ => return Err(StringError::UnknownOption),
}

View File

@@ -25,7 +25,7 @@ impl<'args> StringSubCommand<'args> for Join<'args> {
];
const SHORT_OPTIONS: &'static wstr = L!("qn");
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'q' => self.quiet = true,
'n' => self.no_empty = true,
@@ -44,8 +44,13 @@ fn take_args(
return Ok(());
}
let cmd = L!("string");
let subcmd = args[0];
let Some(arg) = args.get(*optind).copied() else {
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, args[0]);
err_str!(Error::MISSING_ARG)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
*optind += 1;

View File

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

View File

@@ -38,7 +38,7 @@ impl<'args> StringSubCommand<'args> for Match<'args> {
];
const SHORT_OPTIONS: &'static wstr = L!("aegivqrnm:");
fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'a' => self.all = true,
'e' => self.entire = true,
@@ -55,11 +55,7 @@ fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), St
.ok()
.and_then(|v| NonZeroUsize::new(v as usize))
.ok_or_else(|| {
StringError::InvalidArgs(wgettext_fmt!(
BUILTIN_ERR_INVALID_MAX_MATCHES,
_n,
arg
))
StringError::InvalidArgs(err_fmt!(Error::INVALID_MAX_MATCHES, arg))
})?;
Some(max)
}
@@ -75,9 +71,12 @@ fn take_args(
args: &[&'args wstr],
streams: &mut IoStreams,
) -> Result<(), ErrorCode> {
let cmd = args[0];
let cmd = L!("string");
let subcmd = args[0];
let Some(arg) = args.get(*optind).copied() else {
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, cmd);
err_fmt!(Error::MISSING_ARG)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
*optind += 1;
@@ -92,32 +91,36 @@ fn handle(
optind: &mut usize,
args: &[&wstr],
) -> Result<(), ErrorCode> {
let cmd = args[0];
let cmd = L!("string");
let subcmd = args[0];
if self.entire && self.index {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
wgettext!("--entire and --index are mutually exclusive")
));
)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if self.invert_match && self.groups_only {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
wgettext!("--invert and --groups-only are mutually exclusive")
));
)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if self.entire && self.groups_only {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
wgettext!("--entire and --groups-only are mutually exclusive")
));
)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}

View File

@@ -32,23 +32,18 @@ impl StringSubCommand<'_> for Pad {
];
const SHORT_OPTIONS: &'static wstr = L!("c:rCw:");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'c' => {
let [pad_char] = arg.unwrap().as_char_slice() else {
return Err(invalid_args!(
"%s: Padding should be a character '%s'",
name,
arg
));
let arg = arg.unwrap();
let [pad_char] = arg.as_char_slice() else {
return Err(err_fmt!("Padding should be a character '%s'", arg).into());
};
self.pad_char_width = match fish_wcwidth(*pad_char) {
None | Some(0) => {
return Err(invalid_args!(
"%s: Invalid padding character of width zero '%s'",
name,
arg
));
return Err(
err_fmt!("Invalid padding character of width zero '%s'", arg).into(),
);
}
Some(w) => w,
};
@@ -56,9 +51,10 @@ fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(),
}
'r' => self.pad_from = Direction::Right,
'w' => {
self.width = fish_wcstol(arg.unwrap())?
let arg = arg.unwrap();
self.width = fish_wcstol(arg)?
.try_into()
.map_err(|_| invalid_args!("%s: Invalid width value '%s'", name, arg))?;
.map_err(|_| err_fmt!("Invalid width value '%s'", arg))?;
}
'C' => self.center = true,
_ => return Err(StringError::UnknownOption),

View File

@@ -17,20 +17,22 @@ impl StringSubCommand<'_> for Repeat {
];
const SHORT_OPTIONS: &'static wstr = L!("n:m:qN");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'n' => {
let arg = arg.unwrap();
self.count = Some(
fish_wcstol(arg.unwrap())?
fish_wcstol(arg)?
.try_into()
.map_err(|_| invalid_args!("%s: Invalid count value '%s'", name, arg))?,
.map_err(|_| err_fmt!("Invalid count value '%s'", arg))?,
);
}
'm' => {
let arg = arg.unwrap();
self.max = Some(
fish_wcstol(arg.unwrap())?
fish_wcstol(arg)?
.try_into()
.map_err(|_| invalid_args!(BUILTIN_ERR_INVALID_MAX_VALUE, name, arg))?,
.map_err(|_| err_fmt!(Error::INVALID_MAX_VALUE, arg))?,
);
}
'q' => self.quiet = true,
@@ -50,16 +52,21 @@ fn take_args(
return Ok(());
}
let name = args[0];
let cmd = L!("string");
let subcmd = args[0];
let Some(arg) = args.get(*optind) else {
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, name);
err_fmt!(Error::MISSING_ARG)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
*optind += 1;
let Ok(Ok(count)) = fish_wcstol(arg).map(|count| count.try_into()) else {
string_error!(streams, "%s: Invalid count value '%s'", name, arg);
err_fmt!("Invalid count value '%s'", arg)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};

View File

@@ -28,7 +28,7 @@ impl<'args> StringSubCommand<'args> for Replace<'args> {
];
const SHORT_OPTIONS: &'static wstr = L!("afiqrm:");
fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'a' => self.all = true,
'f' => self.filter = true,
@@ -42,11 +42,7 @@ fn parse_opt(&mut self, _n: &wstr, c: char, arg: Option<&wstr>) -> Result<(), St
.ok()
.and_then(|v| NonZeroUsize::new(v as usize))
.ok_or_else(|| {
StringError::InvalidArgs(wgettext_fmt!(
BUILTIN_ERR_INVALID_MAX_MATCHES,
_n,
arg
))
StringError::InvalidArgs(err_fmt!(Error::INVALID_MAX_MATCHES, arg))
})?;
Some(max)
}
@@ -62,14 +58,19 @@ fn take_args(
args: &[&'args wstr],
streams: &mut IoStreams,
) -> Result<(), ErrorCode> {
let cmd = args[0];
let cmd = L!("string");
let subcmd = args[0];
let Some(pattern) = args.get(*optind).copied() else {
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, cmd);
err_str!(Error::MISSING_ARG)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
*optind += 1;
let Some(replacement) = args.get(*optind).copied() else {
string_error!(streams, BUILTIN_ERR_ARG_COUNT1, cmd, 1, 2);
err_fmt!(Error::UNEXP_ARG_COUNT, 1, 2)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
*optind += 1;
@@ -86,7 +87,8 @@ fn handle(
optind: &mut usize,
args: &[&wstr],
) -> Result<(), ErrorCode> {
let cmd = args[0];
let cmd = L!("string");
let subcmd = args[0];
let replacer = match StringReplacer::new(self.pattern, self.replacement, self) {
Ok(x) => x,
@@ -102,12 +104,9 @@ fn handle(
let (replaced, result) = match replacer.replace(arg) {
Ok(x) => x,
Err(e) => {
string_error!(
streams,
"%s: Regular expression substitute error: %s",
cmd,
e.error_message()
);
err_fmt!("Regular expression substitute error: %s", e.error_message())
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
};

View File

@@ -37,22 +37,18 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
];
const SHORT_OPTIONS: &'static wstr = L!("c:m:Nlq");
fn parse_opt(
&mut self,
name: &wstr,
c: char,
arg: Option<&'args wstr>,
) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&'args wstr>) -> Result<(), StringError<'_>> {
match c {
'c' => {
self.ellipsis = arg.unwrap();
self.ellipsis_width = width_without_escapes(self.ellipsis, 0);
}
'm' => {
let arg = arg.unwrap();
self.max = Some(
fish_wcstol(arg.unwrap())?
fish_wcstol(arg)?
.try_into()
.map_err(|_| invalid_args!(BUILTIN_ERR_INVALID_MAX_VALUE, name, arg))?,
.map_err(|_| err_fmt!(Error::INVALID_MAX_VALUE, arg))?,
);
}
'N' => self.no_newline = true,

View File

@@ -112,24 +112,26 @@ impl<'args> StringSubCommand<'args> for Split<'args> {
];
const SHORT_OPTIONS: &'static wstr = L!("qrm:nf:a");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'q' => self.quiet = true,
'r' => self.split_from = Direction::Right,
'm' => {
self.max = fish_wcstol(arg.unwrap())?
let arg = arg.unwrap();
self.max = fish_wcstol(arg)?
.try_into()
.map_err(|_| invalid_args!(BUILTIN_ERR_INVALID_MAX_VALUE, name, arg))?;
.map_err(|_| err_fmt!(Error::INVALID_MAX_VALUE, arg))?;
}
'n' => self.no_empty = true,
'f' => {
self.fields = arg.unwrap().try_into().map_err(|e| match e {
FieldParseError::Number => StringError::NotANumber,
let arg = arg.unwrap();
self.fields = arg.try_into().map_err(|e| match e {
FieldParseError::Number => err_fmt!(Error::NOT_NUMBER, arg),
FieldParseError::Range => {
invalid_args!("%s: Invalid range value for field '%s'", name, arg)
err_fmt!("Invalid range value for field '%s'", arg)
}
FieldParseError::Field => {
invalid_args!("%s: Invalid fields value '%s'", name, arg)
err_fmt!("Invalid fields value '%s'", arg)
}
})?;
}
@@ -148,8 +150,13 @@ fn take_args(
if self.is_split0 {
return Ok(());
}
let cmd = L!("string");
let subcmd = args[0];
let Some(arg) = args.get(*optind).copied() else {
string_error!(streams, BUILTIN_ERR_ARG_COUNT0, args[0]);
err_str!(Error::MISSING_ARG)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
*optind += 1;
@@ -164,12 +171,15 @@ fn handle(
optind: &mut usize,
args: &[&'args wstr],
) -> Result<(), ErrorCode> {
let cmd = L!("string");
let subcmd = args[0];
if self.fields.is_empty() && self.allow_empty {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
args[0],
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
wgettext!("--allow-empty is only valid with --fields")
));
)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}

View File

@@ -19,27 +19,30 @@ impl StringSubCommand<'_> for Sub {
];
const SHORT_OPTIONS: &'static wstr = L!("l:qs:e:");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'l' => {
let arg = arg.unwrap();
self.length = Some(
fish_wcstol(arg.unwrap())?
fish_wcstol(arg)?
.try_into()
.map_err(|_| invalid_args!("%s: Invalid length value '%s'", name, arg))?,
.map_err(|_| err_fmt!("Invalid length value '%s'", arg))?,
);
}
's' => {
let arg = arg.unwrap();
self.start = Some(
fish_wcstol(arg.unwrap())?
fish_wcstol(arg)?
.try_into()
.map_err(|_| invalid_args!("%s: Invalid start value '%s'", name, arg))?,
.map_err(|_| err_fmt!("Invalid start value '%s'", arg))?,
);
}
'e' => {
let arg = arg.unwrap();
self.end = Some(
fish_wcstol(arg.unwrap())?
fish_wcstol(arg)?
.try_into()
.map_err(|_| invalid_args!("%s: Invalid end value '%s'", name, arg))?,
.map_err(|_| err_fmt!("Invalid end value '%s'", arg))?,
);
}
'q' => self.quiet = true,
@@ -55,13 +58,15 @@ fn handle(
optind: &mut usize,
args: &[&wstr],
) -> Result<(), ErrorCode> {
let cmd = args[0];
let cmd = L!("string");
let subcmd = args[0];
if self.length.is_some() && self.end.is_some() {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2,
cmd,
err_fmt!(
Error::INVALID_OPT_COMBO_WITH_CTX,
wgettext!("--end and --length are mutually exclusive")
));
)
.subcmd(cmd, subcmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}

View File

@@ -8,7 +8,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");
fn parse_opt(&mut self, _n: &wstr, c: char, _arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'q' => self.quiet = true,
_ => return Err(StringError::UnknownOption),

View File

@@ -28,12 +28,7 @@ impl<'args> StringSubCommand<'args> for Trim<'args> {
];
const SHORT_OPTIONS: &'static wstr = L!("c:lrq");
fn parse_opt(
&mut self,
_n: &wstr,
c: char,
arg: Option<&'args wstr>,
) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&'args wstr>) -> Result<(), StringError<'_>> {
match c {
'c' => self.chars_to_trim = arg.unwrap(),
'l' => self.left = true,

View File

@@ -16,14 +16,14 @@ impl StringSubCommand<'_> for Unescape {
];
const SHORT_OPTIONS: &'static wstr = L!("n");
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<'_>> {
match c {
'n' => self.no_quoted = true,
NON_OPTION_CHAR => {
let arg = arg.unwrap();
self.style = arg
.unwrap()
.try_into()
.map_err(|_| invalid_args!("%s: Invalid style value '%s'", name, arg))?;
.map_err(|_| err_fmt!("Invalid style value '%s'", arg))?;
}
_ => return Err(StringError::UnknownOption),
}

View File

@@ -1,5 +1,5 @@
use super::prelude::*;
use crate::should_flog;
use crate::{err_str, should_flog};
use fish_feature_flags::{FeatureFlag, feature_test};
mod test_expressions {
@@ -7,6 +7,8 @@ mod test_expressions {
use super::*;
use crate::builtins::error;
use crate::err_raw;
use crate::nix::isatty;
use crate::wutil::{
Error, Options, file_id_for_path, lwstat, waccess, wcstod::wcstod, wcstoi_opts, wstat,
@@ -706,7 +708,7 @@ fn parse_expression(&mut self, start: usize, end: usize) -> Option<Box<dyn Expre
let argc = end - start;
match argc {
0 => {
panic!("argc should not be zero"); // should have been caught by the above test
unreachable!("argc should not be zero"); // should have been caught by the above test
}
1 => self.error(
start + 1,
@@ -719,67 +721,59 @@ fn parse_expression(&mut self, start: usize, end: usize) -> Option<Box<dyn Expre
}
}
pub fn parse_args(
args: &[WString],
err: &mut WString,
program_name: &wstr,
) -> Option<Box<dyn Expression>> {
pub fn parse_args(args: &[WString]) -> Result<Box<dyn Expression>, error::Error<'_>> {
let mut parser = TestParser {
strings: args,
errors: Vec::new(),
error_idx: 0,
};
let mut result = parser.parse_expression(0, args.len());
let result_opt = parser.parse_expression(0, args.len());
// Historic assumption from C++: if we have no errors then we must have a result.
assert!(!parser.errors.is_empty() || result.is_some());
assert!(!parser.errors.is_empty() || result_opt.is_some());
if let Some(result) = result_opt {
let range_end = result.range().end;
assert!(range_end <= args.len());
// The result is valid only if we consumed all the arguments.
// This is not detected by parse_expression(), so in that case
// we need to create our own error.
if range_end == args.len() {
return Ok(result);
}
if parser.errors.is_empty() {
parser.error_idx = range_end;
parser.errors = vec![sprintf!(
"unexpected argument at index %u: '%s'",
range_end + 1,
args[range_end],
)];
}
}
// Handle errors.
// For now we only show the first error.
if !parser.errors.is_empty() || result.as_ref().unwrap().range().end < args.len() {
let mut narg = 0;
let mut len_to_err = 0;
if parser.errors.is_empty() {
parser.error_idx = result.as_ref().unwrap().range().end;
let mut narg = 0;
let mut len_to_err = 0;
let mut commandline = WString::new();
for arg in args {
if narg > 0 {
commandline.push(' ');
}
let mut commandline = WString::new();
for arg in args {
if narg > 0 {
commandline.push(' ');
}
commandline.push_utfstr(arg);
narg += 1;
if narg == parser.error_idx {
len_to_err = fish_wcswidth(&commandline).unwrap_or_default();
}
}
err.push_utfstr(program_name);
err.push_str(": ");
if !parser.errors.is_empty() {
err.push_utfstr(&parser.errors[0]);
} else {
sprintf!(
=> err,
"unexpected argument at index %u: '%s'",
result.as_ref().unwrap().range().end + 1,
args[result.as_ref().unwrap().range().end],
);
}
err.push('\n');
err.push_utfstr(&commandline);
err.push('\n');
err.push_utfstr(&sprintf!("%*s%s\n", len_to_err + 1, " ", "^"));
}
if result.is_some() {
// It's also an error if there are any unused arguments. This is not detected by
// parse_expression().
assert!(result.as_ref().unwrap().range().end <= args.len());
if result.as_ref().unwrap().range().end < args.len() {
result = None;
commandline.push_utfstr(arg);
narg += 1;
if narg == parser.error_idx {
len_to_err = fish_wcswidth(&commandline).unwrap_or_default();
}
}
result
let mut err = WString::new();
err.push_utfstr(&parser.errors[0]);
err.push('\n');
err.push_utfstr(&commandline);
err.push('\n');
err.push_utfstr(&sprintf!("%*s%s\n", len_to_err + 1, " ", "^"));
Err(err_raw!(err))
}
}
@@ -1009,10 +1003,10 @@ pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
// Ignore the closing bracket from now on.
argc -= 1;
} else {
streams
.err
.appendln(wgettext!("[: the last argument must be ']'"));
builtin_print_error_trailer(parser, streams.err, program_name);
err_str!("the last argument must be ']'")
.cmd(program_name)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
@@ -1023,11 +1017,10 @@ pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
if feature_test(FeatureFlag::TestRequireArg) {
if argc == 0 {
streams.err.appendln(&wgettext_fmt!(
"%s: Expected at least one argument",
program_name
));
builtin_print_error_trailer(parser, streams.err, program_name);
err_str!("Expected at least one argument")
.cmd(program_name)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
} else if argc == 1 {
if args[0] == "-n" {
@@ -1038,20 +1031,18 @@ pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
}
} else if argc == 0 {
if should_flog!(deprecated_test) {
streams.err.appendln(&wgettext_fmt!(
"%s: called with no arguments. This will be an error in future.",
program_name
));
streams.err.append(&parser.current_line());
err_str!("called with no arguments. This will be an error in future.")
.cmd(program_name)
.stacktrace(parser)
.finish(streams);
}
return Err(STATUS_INVALID_ARGS); // Per 1003.1, exit false.
} else if argc == 1 {
if should_flog!(deprecated_test) && args[0] != "-z" {
streams.err.appendln(&wgettext_fmt!(
"%s: called with one argument. This will return false in future.",
program_name
));
streams.err.append(&parser.current_line());
err_str!("called with one argument. This will return false in future.")
.cmd(program_name)
.stacktrace(parser)
.finish(streams);
}
// Per 1003.1, exit true if the arg is non-empty.
return if args[0].is_empty() {
@@ -1062,12 +1053,13 @@ pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
}
// Try parsing
let mut err = WString::new();
let expr = test_expressions::TestParser::parse_args(args, &mut err, program_name);
let Some(expr) = expr else {
streams.err.append(&err);
streams.err.append(&parser.current_line());
return Err(STATUS_CMD_ERROR);
let expr = test_expressions::TestParser::parse_args(args);
let expr = match expr {
Ok(expr) => expr,
Err(err) => {
err.cmd(program_name).stacktrace(parser).finish(streams);
return Err(STATUS_CMD_ERROR);
}
};
let mut eval_errors = Vec::new();

View File

@@ -1,9 +1,10 @@
use super::prelude::*;
use crate::builtins::error::Error;
use crate::common::bytes2wcstring;
use crate::function;
use crate::highlight::highlight_and_colorize;
use crate::parse_util::{apply_indents, compute_indents};
use crate::path::{path_get_path, path_get_paths};
use crate::{err_fmt, err_str, function};
#[derive(Default)]
struct type_cmd_opts_t {
@@ -52,7 +53,14 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
builtin_missing_argument(
parser,
streams,
cmd,
None,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -84,7 +92,7 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
.count()
> 1
{
streams.err.appendln(&wgettext_fmt!(BUILTIN_ERR_COMBO, cmd));
err_str!(Error::INVALID_OPT_COMBO).cmd(cmd).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -229,9 +237,9 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B
}
if found == 0 && !opts.query && !opts.path {
streams
.err
.appendln(&wgettext_fmt!("%s: Could not find '%s'", L!("type"), arg));
err_fmt!("Could not find '%s'", arg)
.cmd(cmd)
.finish(streams);
}
}

View File

@@ -4,7 +4,7 @@
use nix::errno::Errno;
use nix::sys::resource::Resource as ResourceEnum;
use crate::wutil::perror_nix;
use crate::{builtins::error::Error, err_fmt, err_raw, err_str, wutil::perror_nix};
use fish_fallback::{fish_wcswidth, wcscasecmp};
use super::prelude::*;
@@ -180,6 +180,8 @@ fn set_limit(
value: rlim_t,
streams: &mut IoStreams,
) -> BuiltinResult {
let cmd = L!("ulimit");
let Some((mut rlim_cur, mut rlim_max)) = getrlimit(resource) else {
return Err(STATUS_CMD_ERROR);
};
@@ -197,12 +199,14 @@ fn set_limit(
if let Err(errno) = setrlimit(resource, rlim_cur, rlim_max) {
if errno == Errno::EPERM {
streams.err.appendln(&wgettext_fmt!(
"ulimit: Permission denied when changing resource of type '%s'",
err_fmt!(
"Permission denied when changing resource of type '%s'",
get_desc(resource)
));
)
.cmd(cmd)
.finish(streams);
} else {
builtin_wperror(L!("ulimit"), streams);
err_raw!(builtin_strerror()).cmd(cmd).finish(streams);
}
Err(STATUS_CMD_ERROR)
@@ -312,7 +316,14 @@ pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
return Ok(SUCCESS);
}
':' => {
builtin_missing_argument(parser, streams, cmd, w.argv[w.wopt_index - 1], true);
builtin_missing_argument(
parser,
streams,
cmd,
None,
w.argv[w.wopt_index - 1],
true,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -334,12 +345,10 @@ pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
if opts.what == -1 {
streams.err.appendln(&wgettext_fmt!(
"%s: Resource limit not available on this operating system",
cmd
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!("Resource limit not available on this operating system")
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -351,11 +360,10 @@ pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
print(what, opts.hard, streams);
return Ok(SUCCESS);
} else if arg_count != 1 {
streams
.err
.appendln(&wgettext_fmt!(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd));
builtin_print_error_trailer(parser, streams.err, cmd);
err_str!(Error::TOO_MANY_ARGUMENTS)
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
@@ -367,7 +375,7 @@ pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
soft = true;
}
localizable_consts! {
BUILTIN_ULIMIT_INVALID "%s: Invalid limit '%s'"
BUILTIN_ULIMIT_INVALID "Invalid limit '%s'"
}
let new_limit: rlim_t = if wcscasecmp(w.argv[w.wopt_index], L!("unlimited")) == Ordering::Equal
@@ -385,22 +393,18 @@ pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
} else if let Ok(limit) = fish_wcstol(w.argv[w.wopt_index]) {
let Some(x) = get_multiplier(what).checked_mul(limit as rlim_t) else {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ULIMIT_INVALID,
cmd,
w.argv[w.wopt_index]
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(BUILTIN_ULIMIT_INVALID, w.argv[w.wopt_index])
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
x
} else {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ULIMIT_INVALID,
cmd,
w.argv[w.wopt_index]
));
builtin_print_error_trailer(parser, streams.err, cmd);
err_fmt!(BUILTIN_ULIMIT_INVALID, w.argv[w.wopt_index])
.cmd(cmd)
.full_trailer(parser)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};

View File

@@ -1,4 +1,5 @@
use super::prelude::*;
use crate::err_fmt;
use crate::proc::{Job, Pid, proc_wait_any};
use crate::signal::SigChecker;
use crate::wait_handle::{WaitHandleRef, WaitHandleStore};
@@ -156,7 +157,14 @@ pub fn wait(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
print_help = true;
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
builtin_missing_argument(
parser,
streams,
cmd,
None,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
@@ -199,20 +207,16 @@ pub fn wait(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui
continue;
};
if !find_wait_handles(WaitHandleQuery::Pid(pid), parser, &mut wait_handles) {
streams.err.appendln(&wgettext_fmt!(
"%s: Could not find a job with process ID '%d'",
cmd,
pid,
));
err_fmt!("Could not find a job with process ID '%d'", pid,)
.cmd(cmd)
.finish(streams);
}
} else {
// argument is process name
if !find_wait_handles(WaitHandleQuery::ProcName(item), parser, &mut wait_handles) {
streams.err.appendln(&wgettext_fmt!(
"%s: Could not find child processes with the name '%s'",
cmd,
item,
));
err_fmt!("Could not find child processes with the name '%s'", item,)
.cmd(cmd)
.finish(streams);
}
}
}

View File

@@ -669,6 +669,17 @@ pub fn contents(&self) -> &wstr {
}
}
/// Consume and return any internally buffered contents.
/// This is only implemented for a string_output_stream; others will return an empty string.
pub fn take(self) -> WString {
match self {
OutputStream::String(stream) => stream.take(),
OutputStream::Null | OutputStream::Fd(_) | OutputStream::Buffered(_) => {
WString::default()
}
}
}
/// Flush any unwritten data to the underlying device, and return an error code.
/// A 0 code indicates success. The base implementation returns 0.
pub fn flush_and_check_error(&mut self) -> libc::c_int {
@@ -786,6 +797,11 @@ fn append(&mut self, s: impl IntoCharIter) -> bool {
fn contents(&self) -> &wstr {
&self.contents
}
/// Consume and return the wcstring containing the output.
fn take(self) -> WString {
self.contents
}
}
/// An output stream for builtins which writes into a separated buffer.

View File

@@ -4,11 +4,10 @@
self, BlockStatementHeader, Keyword as _, Leaf as _, Node, Statement, Token as _,
unescape_keyword,
};
use crate::builtins;
use crate::builtins::error::Error;
use crate::builtins::shared::{
BUILTIN_ERR_VARNAME, STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, STATUS_EXPAND_ERROR,
STATUS_ILLEGAL_CMD, STATUS_INVALID_ARGS, STATUS_NOT_EXECUTABLE, STATUS_UNMATCHED_WILDCARD,
builtin_exists,
STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, STATUS_EXPAND_ERROR, STATUS_ILLEGAL_CMD,
STATUS_INVALID_ARGS, STATUS_NOT_EXECUTABLE, STATUS_UNMATCHED_WILDCARD, builtin_exists,
};
use crate::common::{
ScopeGuard, ScopeGuarding, ScopedRefCell, escape, truncate_at_nul, valid_var_name,
@@ -52,6 +51,7 @@
use crate::tokenizer::{PipeOrRedir, TokenType, variable_assignment_equals_pos};
use crate::trace::{trace_if_enabled, trace_if_enabled_with_args};
use crate::wildcard::wildcard_match;
use crate::{builtins, err_fmt};
use fish_common::help_section;
use fish_widestring::WExt as _;
use libc::{ENOTDIR, EXIT_SUCCESS, STDERR_FILENO, STDOUT_FILENO, c_int};
@@ -117,15 +117,13 @@ macro_rules! report_error_formatted {
}};
}
pub fn varname_error(command: &wstr, bad_name: &wstr) -> WString {
let mut e = wgettext_fmt!(
BUILTIN_ERR_VARNAME,
command,
pub fn varname_error<'a>(command: &'a wstr, bad_name: &'a wstr) -> Error<'a> {
err_fmt!(
Error::INVALID_VARNAME,
bad_name,
help_section!("language#shell-variable-and-function-names")
);
e.push('\n');
e
)
.cmd(command)
}
impl<'a> ExecutionContext<'a> {
@@ -909,7 +907,7 @@ fn run_for_statement(
STATUS_INVALID_ARGS,
header.var_name,
"%s",
varname_error(L!("for"), &for_var_name)
&varname_error(L!("for"), &for_var_name).to_string()
);
}

View File

@@ -10,19 +10,19 @@ history --search --merge
history --clear --contains
#CHECKERR: history: clear: subcommand takes no options
history --merge -t
#CHECKERR: history: merge: subcommand takes no options
#CHECKERR: history merge: subcommand takes no options
history --save xyz
#CHECKERR: history: save: expected 0 arguments; got 1
#CHECKERR: history save: expected 0 arguments; got 1
# Now with the history builtin.
builtin history --save --prefix
#CHECKERR: history: save: subcommand takes no options
#CHECKERR: history save: subcommand takes no options
builtin history --clear --show-time
#CHECKERR: history: clear: subcommand takes no options
#CHECKERR: history clear: subcommand takes no options
builtin history --merge xyz
#CHECKERR: history: merge: expected 0 arguments; got 1
#CHECKERR: history merge: expected 0 arguments; got 1
builtin history --clear abc def
#CHECKERR: history: clear: expected 0 arguments; got 2
#CHECKERR: history clear: expected 0 arguments; got 2
# Now using the preferred subcommand form. Note that we support flags before
# or after the subcommand name so test both variants.
@@ -31,37 +31,37 @@ builtin history --clear abc def
history clear --contains
#CHECKERR: history: clear: subcommand takes no options
history clear-session --contains
#CHECKERR: history: clear-session: subcommand takes no options
#CHECKERR: history clear-session: subcommand takes no options
history merge -t
#CHECKERR: history: merge: subcommand takes no options
#CHECKERR: history merge: subcommand takes no options
history save xyz
#CHECKERR: history: save: expected 0 arguments; got 1
#CHECKERR: history save: expected 0 arguments; got 1
history --prefix clear
#CHECKERR: history: clear: subcommand takes no options
history --prefix clear-session
#CHECKERR: history: clear-session: subcommand takes no options
#CHECKERR: history clear-session: subcommand takes no options
history --show-time merge
#CHECKERR: history: merge: subcommand takes no options
#CHECKERR: history merge: subcommand takes no options
# Now with the history builtin.
builtin history --search --merge
#CHECKERR: history: search merge: options cannot be used together
builtin history save --prefix
#CHECKERR: history: save: subcommand takes no options
#CHECKERR: history save: subcommand takes no options
builtin history clear --show-time
#CHECKERR: history: clear: subcommand takes no options
#CHECKERR: history clear: subcommand takes no options
builtin history clear-session --show-time
#CHECKERR: history: clear-session: subcommand takes no options
#CHECKERR: history clear-session: subcommand takes no options
builtin history merge xyz
#CHECKERR: history: merge: expected 0 arguments; got 1
#CHECKERR: history merge: expected 0 arguments; got 1
builtin history clear abc def
#CHECKERR: history: clear: expected 0 arguments; got 2
#CHECKERR: history clear: expected 0 arguments; got 2
builtin history clear-session abc def
#CHECKERR: history: clear-session: expected 0 arguments; got 2
#CHECKERR: history clear-session: expected 0 arguments; got 2
builtin history --contains save
#CHECKERR: history: save: subcommand takes no options
#CHECKERR: history save: subcommand takes no options
builtin history -t merge
#CHECKERR: history: merge: subcommand takes no options
#CHECKERR: history merge: subcommand takes no options
# Now do a history command that should succeed so we exit with a zero,
# success, status.

View File

@@ -378,7 +378,7 @@ path
# CHECKERR: (Type 'help path' for related documentation)
path invalid-subcmd
# CHECKERR: path: invalid-subcmd: invalid subcommand
# CHECKERR: path invalid-subcmd: invalid subcommand
# CHECKERR: {{.*}}/checks/path.fish (line {{\d+}}):
# CHECKERR: path invalid-subcmd
# CHECKERR: ^

View File

@@ -11,6 +11,6 @@ function __fish_print_help
return 2
end
builtin and --help
# CHECKERR: fish: and: missing man page
# CHECKERR: and: missing man page
# CHECKERR: Documentation may not be installed.
# CHECKERR: `help and` will show an online version

View File

@@ -9,7 +9,7 @@ set -l data_home_realpath (builtin realpath $XDG_DATA_HOME)
if not builtin realpath /this/better/be/an/invalid/path
echo first invalid path handled okay
# CHECK: first invalid path handled okay
# CHECKERR: builtin realpath: /this/better/be/an/invalid/path: No such file or directory
# CHECKERR: realpath: /this/better/be/an/invalid/path: No such file or directory
end
# A non-existent file relative to $PWD succeeds.
@@ -50,9 +50,9 @@ cd subdir
# But that's what we want to test, so we weasel around it.
sh -c "cd ../..; rmdir $tmpdir/subdir $tmpdir"
builtin realpath .
# CHECKERR: builtin realpath: .: No such file or directory
# CHECKERR: realpath: .: No such file or directory
builtin realpath -s .
# CHECKERR: builtin realpath: realpath failed: No such file or directory
# CHECKERR: realpath: realpath failed: No such file or directory
popd
# A single symlink to a directory is correctly resolved.
@@ -139,10 +139,10 @@ builtin realpath / /
# CHECK: /
builtin realpath '' /tmp '' /dont-exist ''
# CHECKERR: builtin realpath: Invalid arg:
# CHECKERR: realpath: Invalid arg:
# CHECK: {{.*}}/tmp
# CHECKERR: builtin realpath: Invalid arg:
# CHECKERR: realpath: Invalid arg:
# CHECK: /dont-exist
# CHECKERR: builtin realpath: Invalid arg:
# CHECKERR: realpath: Invalid arg:
exit 0

View File

@@ -1065,16 +1065,16 @@ echo Still here
# CHECK: Still here
set -o xtrace
# CHECKERR: Fish does not have shell options. See `help fish_for_bash_users`.
# CHECKERR: fish does not have shell options. See `help fish_for_bash_users`.
# CHECKERR: set: -o: unknown option
set -o vi
# CHECKERR: Fish does not have shell options. See `help fish_for_bash_users`.
# CHECKERR: fish does not have shell options. See `help fish_for_bash_users`.
# CHECKERR: To enable vi-mode, run `fish_vi_key_bindings`.
# CHECKERR: set: -o: unknown option
set -o ed
# CHECKERR: Fish does not have shell options. See `help fish_for_bash_users`.
# CHECKERR: fish does not have shell options. See `help fish_for_bash_users`.
# CHECKERR: ?
# CHECKERR: ?
# CHECKERR: ?

View File

@@ -31,7 +31,7 @@ status -b is-interactive
# Try to set the job control to an invalid mode.
status job-control full1
#CHECKERR: status: Invalid job control mode 'full1'
#CHECKERR: status job-control: Invalid job control mode 'full1'
status --job-control=1none
#CHECKERR: status: Invalid job control mode '1none'
@@ -131,10 +131,10 @@ printf "%s\n" (test-stack-trace-copy | string replace \t '<TAB>')[1..4]
status test-terminal-feature
and should have failed on missing arg
# CHECKERR: status: test-terminal-feature: expected 1 arguments; got 0
# CHECKERR: status test-terminal-feature: expected 1 arguments; got 0
status test-terminal-feature 1 2
and should have failed on too many args
# CHECKERR: status: test-terminal-feature: expected 1 arguments; got 2
# CHECKERR: status test-terminal-feature: expected 1 arguments; got 2
status test-terminal-feature unrecognized-feature
and should have failed on unrecognized feature
# CHECKERR: status test-terminal-feature: unrecognized feature 'unrecognized-feature'
@@ -147,20 +147,20 @@ status -L 9999999999999999999999
# CHECKERR: status: Invalid level value '9999999999999999999999'
status unknown-subcmd
# CHECKERR: status: unknown-subcmd: invalid subcommand
# CHECKERR: status unknown-subcmd: invalid subcommand
status job-control abc cdf
# CHECKERR: status: job-control: expected 1 arguments; got 2
# CHECKERR: status job-control: expected 1 arguments; got 2
status test-feature
# CHECKERR: status: test-feature: expected 1 arguments; got 0
# CHECKERR: status test-feature: expected 1 arguments; got 0
status test-feature one two
# CHECKERR: status: test-feature: expected 1 arguments; got 2
# CHECKERR: status test-feature: expected 1 arguments; got 2
status get-file
# CHECKERR: status: get-file: expected 1 arguments; got 0
# CHECKERR: status get-file: expected 1 arguments; got 0
status get-file one two
# CHECKERR: status: get-file: expected 1 arguments; got 2
# CHECKERR: status get-file: expected 1 arguments; got 2
if status buildinfo | string match -q "*localize-messages*"
echo Skipped
@@ -187,4 +187,4 @@ end
# CHECK: {{Skipped|Success}}
status build-info other-arg
# CHECKERR: status: build-info: expected 0 arguments; got 1
# CHECKERR: status build-info: expected 0 arguments; got 1

View File

@@ -9,14 +9,14 @@ string
# CHECKERR: (Type 'help string' for related documentation)
string abc
# CHECKERR: string: abc: invalid subcommand
# CHECKERR: string abc: invalid subcommand
# CHECKERR: {{.*}}checks/string.fish (line {{\d+}}):
# CHECKERR: string abc
# CHECKERR: ^
# CHECKERR: (Type 'help string' for related documentation)
string --abc
# CHECKERR: string: --abc: invalid subcommand
# CHECKERR: string --abc: invalid subcommand
# CHECKERR: {{.*}}checks/string.fish (line {{\d+}}):
# CHECKERR: string --abc
# CHECKERR: ^
@@ -58,7 +58,7 @@ string match -q -r -v x x; or echo "exit 1"
# CHECK: exit 1
string match -v -g foo foo
# CHECKERR: match: invalid option combination, --invert and --groups-only are mutually exclusive
# CHECKERR: string match: invalid option combination, --invert and --groups-only are mutually exclusive
string match
# CHECKERR: string match: missing argument
@@ -251,7 +251,7 @@ string sub -s 2 -e -5 abcde
# CHECK:
string sub -s 2 -e -5 -l 3 abcde
# CHECKERR: sub: invalid option combination, --end and --length are mutually exclusive
# CHECKERR: string sub: invalid option combination, --end and --length are mutually exclusive
string split . example.com
# CHECK: example
@@ -336,7 +336,7 @@ string split --allow-empty --fields=2,9 "" abc
# CHECK: b
string split --allow-empty "" abc
# CHECKERR: split: invalid option combination, --allow-empty is only valid with --fields
# CHECKERR: string split: invalid option combination, --allow-empty is only valid with --fields
seq 3 | string join ...
# CHECK: 1...2...3
@@ -783,10 +783,10 @@ or echo exit 1
# CHECK: xyx
string match --entire --index foo foo
# CHECKERR: match: invalid option combination, --entire and --index are mutually exclusive
# CHECKERR: string match: invalid option combination, --entire and --index are mutually exclusive
string match --entire --groups-only -r foo foo
# CHECKERR: match: invalid option combination, --entire and --groups-only are mutually exclusive
# CHECKERR: string match: invalid option combination, --entire and --groups-only are mutually exclusive
# 'string match -r "a*b([xy]+)" abc abxc bye aaabyz kaabxz abbxy abcx caabxyxz'
string match -r "a*b([xy]+)" abc abxc bye aaabyz kaabxz abbxy abcx caabxyxz