mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-14 05:53:59 +00:00
Port builtins/status to fish
- Also port tests of wdirname and wbasename, as they were bugged
This commit is contained in:
parent
37337683cb
commit
7b3637cd1f
14 changed files with 781 additions and 24 deletions
16
fish-rust/Cargo.lock
generated
16
fish-rust/Cargo.lock
generated
|
@ -341,6 +341,7 @@ dependencies = [
|
||||||
"lru",
|
"lru",
|
||||||
"moveit",
|
"moveit",
|
||||||
"nix",
|
"nix",
|
||||||
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pcre2",
|
"pcre2",
|
||||||
|
@ -630,6 +631,17 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -648,7 +660,7 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pcre2"
|
name = "pcre2"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
source = "git+https://github.com/fish-shell/rust-pcre2?branch=master#824dd1460562f7b724a9acef218d4edb2ed7c289"
|
source = "git+https://github.com/fish-shell/rust-pcre2?branch=master#813a4267546e5ca8ff349c9c67d65e52a82172d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
@ -659,7 +671,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pcre2-sys"
|
name = "pcre2-sys"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
source = "git+https://github.com/fish-shell/rust-pcre2?branch=master#824dd1460562f7b724a9acef218d4edb2ed7c289"
|
source = "git+https://github.com/fish-shell/rust-pcre2?branch=master#813a4267546e5ca8ff349c9c67d65e52a82172d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
|
@ -22,6 +22,8 @@ lru = "0.10.0"
|
||||||
moveit = "0.5.1"
|
moveit = "0.5.1"
|
||||||
nix = { version = "0.25.0", default-features = false, features = [] }
|
nix = { version = "0.25.0", default-features = false, features = [] }
|
||||||
num-traits = "0.2.15"
|
num-traits = "0.2.15"
|
||||||
|
# to make integer->enum conversion easier
|
||||||
|
num-derive = "0.3.3"
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||||
unixstring = "0.2.7"
|
unixstring = "0.2.7"
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub mod random;
|
||||||
pub mod realpath;
|
pub mod realpath;
|
||||||
pub mod r#return;
|
pub mod r#return;
|
||||||
pub mod set_color;
|
pub mod set_color;
|
||||||
|
pub mod status;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
pub mod r#type;
|
pub mod r#type;
|
||||||
pub mod wait;
|
pub mod wait;
|
||||||
|
|
|
@ -37,6 +37,9 @@ pub const BUILTIN_ERR_TOO_MANY_ARGUMENTS: &str = "%ls: too many arguments\n";
|
||||||
/// Error message when integer expected
|
/// Error message when integer expected
|
||||||
pub const BUILTIN_ERR_NOT_NUMBER: &str = "%ls: %ls: invalid integer\n";
|
pub const BUILTIN_ERR_NOT_NUMBER: &str = "%ls: %ls: invalid integer\n";
|
||||||
|
|
||||||
|
pub const BUILTIN_ERR_MISSING_SUBCMD: &str = "%ls: missing subcommand\n";
|
||||||
|
pub const BUILTIN_ERR_INVALID_SUBCMD: &str = "%ls: %ls: invalid subcommand\n";
|
||||||
|
|
||||||
/// Error message for unknown switch.
|
/// Error message for unknown switch.
|
||||||
pub const BUILTIN_ERR_UNKNOWN: &str = "%ls: %ls: unknown option\n";
|
pub const BUILTIN_ERR_UNKNOWN: &str = "%ls: %ls: unknown option\n";
|
||||||
|
|
||||||
|
@ -50,6 +53,7 @@ pub const BUILTIN_ERR_MAX_ARG_COUNT1: &str = "%ls: expected <= %d arguments; got
|
||||||
/// Error message on invalid combination of options.
|
/// Error message on invalid combination of options.
|
||||||
pub const BUILTIN_ERR_COMBO: &str = "%ls: invalid option combination\n";
|
pub const BUILTIN_ERR_COMBO: &str = "%ls: invalid option combination\n";
|
||||||
pub const BUILTIN_ERR_COMBO2: &str = "%ls: invalid option combination, %ls\n";
|
pub const BUILTIN_ERR_COMBO2: &str = "%ls: invalid option combination, %ls\n";
|
||||||
|
pub const BUILTIN_ERR_COMBO2_EXCLUSIVE: &str = "%ls: %ls %ls: options cannot be used together\n";
|
||||||
|
|
||||||
// Return values (`$status` values for fish scripts) for various situations.
|
// Return values (`$status` values for fish scripts) for various situations.
|
||||||
|
|
||||||
|
@ -197,6 +201,7 @@ pub fn run_builtin(
|
||||||
RustBuiltin::Realpath => super::realpath::realpath(parser, streams, args),
|
RustBuiltin::Realpath => super::realpath::realpath(parser, streams, args),
|
||||||
RustBuiltin::Return => super::r#return::r#return(parser, streams, args),
|
RustBuiltin::Return => super::r#return::r#return(parser, streams, args),
|
||||||
RustBuiltin::SetColor => super::set_color::set_color(parser, streams, args),
|
RustBuiltin::SetColor => super::set_color::set_color(parser, streams, args),
|
||||||
|
RustBuiltin::Status => super::status::status(parser, streams, args),
|
||||||
RustBuiltin::Test => super::test::test(parser, streams, args),
|
RustBuiltin::Test => super::test::test(parser, streams, args),
|
||||||
RustBuiltin::Type => super::r#type::r#type(parser, streams, args),
|
RustBuiltin::Type => super::r#type::r#type(parser, streams, args),
|
||||||
RustBuiltin::Wait => wait::wait(parser, streams, args),
|
RustBuiltin::Wait => wait::wait(parser, streams, args),
|
||||||
|
|
633
fish-rust/src/builtins/status.rs
Normal file
633
fish-rust/src/builtins/status.rs
Normal file
|
@ -0,0 +1,633 @@
|
||||||
|
use std::os::unix::prelude::OsStrExt;
|
||||||
|
|
||||||
|
use crate::builtins::shared::BUILTIN_ERR_NOT_NUMBER;
|
||||||
|
use crate::builtins::shared::{
|
||||||
|
builtin_missing_argument, builtin_print_help, builtin_unknown_option, io_streams_t,
|
||||||
|
BUILTIN_ERR_ARG_COUNT2, BUILTIN_ERR_COMBO2_EXCLUSIVE, BUILTIN_ERR_INVALID_SUBCMD,
|
||||||
|
STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_INVALID_ARGS,
|
||||||
|
};
|
||||||
|
use crate::common::{get_executable_path, str2wcstring};
|
||||||
|
|
||||||
|
use crate::ffi::get_job_control_mode;
|
||||||
|
use crate::ffi::get_login;
|
||||||
|
use crate::ffi::set_job_control_mode;
|
||||||
|
use crate::ffi::{is_interactive_session, Repin};
|
||||||
|
use crate::ffi::{job_control_t, parser_t};
|
||||||
|
use crate::future_feature_flags::{feature_metadata, feature_test};
|
||||||
|
use crate::wchar::{wstr, WString, L};
|
||||||
|
|
||||||
|
use crate::wchar_ffi::WCharFromFFI;
|
||||||
|
|
||||||
|
use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t};
|
||||||
|
use crate::wutil::{
|
||||||
|
fish_wcstoi, waccess, wbasename, wdirname, wgettext, wgettext_fmt, wrealpath, Error,
|
||||||
|
};
|
||||||
|
use libc::{c_int, F_OK};
|
||||||
|
use nix::errno::Errno;
|
||||||
|
use nix::NixPath;
|
||||||
|
use num_derive::FromPrimitive;
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
|
|
||||||
|
macro_rules! str_enum {
|
||||||
|
($name:ident, $(($val:ident, $str:expr)),* $(,)?) => {
|
||||||
|
impl TryFrom<&wstr> for $name {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(s: &wstr) -> Result<Self, Self::Error> {
|
||||||
|
// matching on str's let's us avoid having to do binary search and friends outselves,
|
||||||
|
// this is ascii only anyways
|
||||||
|
match s.to_string().as_str() {
|
||||||
|
$($str => Ok(Self::$val)),*,
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
fn to_wstr(&self) -> WString {
|
||||||
|
// There can be multiple vals => str mappings, and that's okay
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
match self {
|
||||||
|
$(Self::$val => WString::from($str)),*,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use StatusCmd::*;
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Default, PartialEq, FromPrimitive, Clone)]
|
||||||
|
enum StatusCmd {
|
||||||
|
STATUS_CURRENT_CMD = 1,
|
||||||
|
STATUS_BASENAME,
|
||||||
|
STATUS_DIRNAME,
|
||||||
|
STATUS_FEATURES,
|
||||||
|
STATUS_FILENAME,
|
||||||
|
STATUS_FISH_PATH,
|
||||||
|
STATUS_FUNCTION,
|
||||||
|
STATUS_IS_BLOCK,
|
||||||
|
STATUS_IS_BREAKPOINT,
|
||||||
|
STATUS_IS_COMMAND_SUB,
|
||||||
|
STATUS_IS_FULL_JOB_CTRL,
|
||||||
|
STATUS_IS_INTERACTIVE,
|
||||||
|
STATUS_IS_INTERACTIVE_JOB_CTRL,
|
||||||
|
STATUS_IS_LOGIN,
|
||||||
|
STATUS_IS_NO_JOB_CTRL,
|
||||||
|
STATUS_LINE_NUMBER,
|
||||||
|
STATUS_SET_JOB_CONTROL,
|
||||||
|
STATUS_STACK_TRACE,
|
||||||
|
STATUS_TEST_FEATURE,
|
||||||
|
STATUS_CURRENT_COMMANDLINE,
|
||||||
|
#[default]
|
||||||
|
STATUS_UNDEF,
|
||||||
|
}
|
||||||
|
|
||||||
|
str_enum!(
|
||||||
|
StatusCmd,
|
||||||
|
(STATUS_BASENAME, "basename"),
|
||||||
|
(STATUS_BASENAME, "current-basename"),
|
||||||
|
(STATUS_CURRENT_CMD, "current-command"),
|
||||||
|
(STATUS_CURRENT_COMMANDLINE, "current-commandline"),
|
||||||
|
(STATUS_DIRNAME, "current-dirname"),
|
||||||
|
(STATUS_FILENAME, "current-filename"),
|
||||||
|
(STATUS_FUNCTION, "current-function"),
|
||||||
|
(STATUS_LINE_NUMBER, "current-line-number"),
|
||||||
|
(STATUS_DIRNAME, "dirname"),
|
||||||
|
(STATUS_FEATURES, "features"),
|
||||||
|
(STATUS_FILENAME, "filename"),
|
||||||
|
(STATUS_FISH_PATH, "fish-path"),
|
||||||
|
(STATUS_FUNCTION, "function"),
|
||||||
|
(STATUS_IS_BLOCK, "is-block"),
|
||||||
|
(STATUS_IS_BREAKPOINT, "is-breakpoint"),
|
||||||
|
(STATUS_IS_COMMAND_SUB, "is-command-substitution"),
|
||||||
|
(STATUS_IS_FULL_JOB_CTRL, "is-full-job-control"),
|
||||||
|
(STATUS_IS_INTERACTIVE, "is-interactive"),
|
||||||
|
(STATUS_IS_INTERACTIVE_JOB_CTRL, "is-interactive-job-control"),
|
||||||
|
(STATUS_IS_LOGIN, "is-login"),
|
||||||
|
(STATUS_IS_NO_JOB_CTRL, "is-no-job-control"),
|
||||||
|
(STATUS_SET_JOB_CONTROL, "job-control"),
|
||||||
|
(STATUS_LINE_NUMBER, "line-number"),
|
||||||
|
(STATUS_STACK_TRACE, "print-stack-trace"),
|
||||||
|
(STATUS_STACK_TRACE, "stack-trace"),
|
||||||
|
(STATUS_TEST_FEATURE, "test-feature"),
|
||||||
|
// this was a nullptr in C++
|
||||||
|
(STATUS_UNDEF, "undef"),
|
||||||
|
);
|
||||||
|
|
||||||
|
impl StatusCmd {
|
||||||
|
fn as_char(&self) -> char {
|
||||||
|
// TODO: once unwrap is const, make LONG_OPTIONS const
|
||||||
|
let ch: StatusCmd = self.clone();
|
||||||
|
char::from_u32(ch as u32).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Values that may be returned from the test-feature option to status.
|
||||||
|
#[repr(i32)]
|
||||||
|
enum TestFeatureRetVal {
|
||||||
|
TEST_FEATURE_ON = 0,
|
||||||
|
TEST_FEATURE_OFF,
|
||||||
|
TEST_FEATURE_NOT_RECOGNIZED,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StatusCmdOpts {
|
||||||
|
level: i32,
|
||||||
|
new_job_control_mode: Option<job_control_t>,
|
||||||
|
status_cmd: StatusCmd,
|
||||||
|
print_help: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StatusCmdOpts {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
level: 1,
|
||||||
|
new_job_control_mode: None,
|
||||||
|
status_cmd: StatusCmd::STATUS_UNDEF,
|
||||||
|
print_help: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusCmdOpts {
|
||||||
|
fn set_status_cmd(&mut self, cmd: &wstr, sub_cmd: StatusCmd) -> Result<(), WString> {
|
||||||
|
if self.status_cmd != StatusCmd::STATUS_UNDEF {
|
||||||
|
return Err(wgettext_fmt!(
|
||||||
|
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||||
|
cmd,
|
||||||
|
self.status_cmd.to_wstr(),
|
||||||
|
sub_cmd.to_wstr(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
self.status_cmd = sub_cmd;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHORT_OPTIONS: &wstr = L!(":L:cbilfnhj:t");
|
||||||
|
static LONG_OPTIONS: Lazy<[woption; 17]> = Lazy::new(|| {
|
||||||
|
use woption_argument_t::*;
|
||||||
|
[
|
||||||
|
wopt(L!("help"), no_argument, 'h'),
|
||||||
|
wopt(L!("current-filename"), no_argument, 'f'),
|
||||||
|
wopt(L!("current-line-number"), no_argument, 'n'),
|
||||||
|
wopt(L!("filename"), no_argument, 'f'),
|
||||||
|
wopt(L!("fish-path"), no_argument, STATUS_FISH_PATH.as_char()),
|
||||||
|
wopt(L!("is-block"), no_argument, 'b'),
|
||||||
|
wopt(L!("is-command-substitution"), no_argument, 'c'),
|
||||||
|
wopt(
|
||||||
|
L!("is-full-job-control"),
|
||||||
|
no_argument,
|
||||||
|
STATUS_IS_FULL_JOB_CTRL.as_char(),
|
||||||
|
),
|
||||||
|
wopt(L!("is-interactive"), no_argument, 'i'),
|
||||||
|
wopt(
|
||||||
|
L!("is-interactive-job-control"),
|
||||||
|
no_argument,
|
||||||
|
STATUS_IS_INTERACTIVE_JOB_CTRL.as_char(),
|
||||||
|
),
|
||||||
|
wopt(L!("is-login"), no_argument, 'l'),
|
||||||
|
wopt(
|
||||||
|
L!("is-no-job-control"),
|
||||||
|
no_argument,
|
||||||
|
STATUS_IS_NO_JOB_CTRL.as_char(),
|
||||||
|
),
|
||||||
|
wopt(L!("job-control"), required_argument, 'j'),
|
||||||
|
wopt(L!("level"), required_argument, 'L'),
|
||||||
|
wopt(L!("line"), no_argument, 'n'),
|
||||||
|
wopt(L!("line-number"), no_argument, 'n'),
|
||||||
|
wopt(L!("print-stack-trace"), no_argument, 't'),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Print the features and their values.
|
||||||
|
fn print_features(streams: &mut io_streams_t) {
|
||||||
|
// TODO: move this to features.rs
|
||||||
|
let mut max_len = i32::MIN;
|
||||||
|
for md in feature_metadata() {
|
||||||
|
max_len = max_len.max(md.name.len() as i32);
|
||||||
|
}
|
||||||
|
for md in feature_metadata() {
|
||||||
|
let set = if feature_test(md.flag) {
|
||||||
|
L!("on")
|
||||||
|
} else {
|
||||||
|
L!("off")
|
||||||
|
};
|
||||||
|
streams.out.append(wgettext_fmt!(
|
||||||
|
"%-*ls%-3s %ls %ls\n",
|
||||||
|
max_len + 1,
|
||||||
|
md.name.from_ffi(),
|
||||||
|
set,
|
||||||
|
md.groups.from_ffi(),
|
||||||
|
md.description.from_ffi(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cmd_opts(
|
||||||
|
opts: &mut StatusCmdOpts,
|
||||||
|
optind: &mut usize,
|
||||||
|
args: &mut [&wstr],
|
||||||
|
parser: &mut parser_t,
|
||||||
|
streams: &mut io_streams_t,
|
||||||
|
) -> Option<c_int> {
|
||||||
|
let cmd = args[0];
|
||||||
|
|
||||||
|
let mut args_read = Vec::with_capacity(args.len());
|
||||||
|
args_read.extend_from_slice(args);
|
||||||
|
|
||||||
|
let mut w = wgetopter_t::new(SHORT_OPTIONS, &*LONG_OPTIONS, args);
|
||||||
|
while let Some(c) = w.wgetopt_long() {
|
||||||
|
match c {
|
||||||
|
'L' => {
|
||||||
|
opts.level = {
|
||||||
|
let arg = w.woptarg.expect("Option -L requires an argument");
|
||||||
|
match fish_wcstoi(arg) {
|
||||||
|
Ok(level) if level >= 0 => level,
|
||||||
|
Err(Error::Overflow) | Ok(_) => {
|
||||||
|
streams.err.append(wgettext_fmt!(
|
||||||
|
"%ls: Invalid level value '%ls'\n",
|
||||||
|
cmd,
|
||||||
|
arg
|
||||||
|
));
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
streams
|
||||||
|
.err
|
||||||
|
.append(wgettext_fmt!(BUILTIN_ERR_NOT_NUMBER, cmd, arg));
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'c' => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, STATUS_IS_COMMAND_SUB) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'b' => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, STATUS_IS_BLOCK) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'i' => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, STATUS_IS_INTERACTIVE) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'l' => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, STATUS_IS_LOGIN) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'f' => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, STATUS_FILENAME) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'n' => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, STATUS_LINE_NUMBER) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'j' => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, STATUS_SET_JOB_CONTROL) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
let Ok(job_mode) = w.woptarg.unwrap().try_into() else {
|
||||||
|
streams.err.append(wgettext_fmt!("%ls: Invalid job control mode '%ls'\n", cmd, w.woptarg.unwrap()));
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
};
|
||||||
|
opts.new_job_control_mode = Some(job_mode);
|
||||||
|
}
|
||||||
|
't' => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, STATUS_STACK_TRACE) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'h' => opts.print_help = true,
|
||||||
|
':' => {
|
||||||
|
builtin_missing_argument(parser, streams, cmd, args[w.woptind - 1], false);
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
'?' => {
|
||||||
|
builtin_unknown_option(parser, streams, cmd, args[w.woptind - 1], false);
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
c => {
|
||||||
|
let Some(opt_cmd) = StatusCmd::from_u32(c as u32) else {
|
||||||
|
panic!("unexpected retval from wgetopt_long")
|
||||||
|
};
|
||||||
|
match opt_cmd {
|
||||||
|
STATUS_IS_FULL_JOB_CTRL
|
||||||
|
| STATUS_IS_INTERACTIVE_JOB_CTRL
|
||||||
|
| STATUS_IS_NO_JOB_CTRL
|
||||||
|
| STATUS_FISH_PATH => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, opt_cmd) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("unexpected retval from wgetopt_long"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*optind = w.woptind;
|
||||||
|
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(
|
||||||
|
parser: &mut parser_t,
|
||||||
|
streams: &mut io_streams_t,
|
||||||
|
args: &mut [&wstr],
|
||||||
|
) -> Option<c_int> {
|
||||||
|
let cmd = args[0];
|
||||||
|
let argc = args.len();
|
||||||
|
|
||||||
|
let mut opts = StatusCmdOpts::default();
|
||||||
|
let mut optind = 0usize;
|
||||||
|
let retval = parse_cmd_opts(&mut opts, &mut optind, args, parser, streams);
|
||||||
|
if retval != STATUS_CMD_OK {
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.print_help {
|
||||||
|
builtin_print_help(parser, streams, cmd);
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a status command hasn't already been specified via a flag check the first word.
|
||||||
|
// Note that this can be simplified after we eliminate allowing subcommands as flags.
|
||||||
|
if optind < argc {
|
||||||
|
match StatusCmd::try_from(args[optind]) {
|
||||||
|
// TODO: can we replace UNDEF with wrapping in option?
|
||||||
|
Ok(STATUS_UNDEF) | Err(_) => {
|
||||||
|
streams
|
||||||
|
.err
|
||||||
|
.append(wgettext_fmt!(BUILTIN_ERR_INVALID_SUBCMD, cmd, args[1]));
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
Ok(s) => {
|
||||||
|
if let Err(e) = opts.set_status_cmd(cmd, s) {
|
||||||
|
streams.err.append(e);
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
optind += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Every argument that we haven't consumed already is an argument for a subcommand.
|
||||||
|
let args = &args[optind..];
|
||||||
|
|
||||||
|
match opts.status_cmd {
|
||||||
|
STATUS_UNDEF => {
|
||||||
|
if !args.is_empty() {
|
||||||
|
streams.err.append(wgettext_fmt!(
|
||||||
|
BUILTIN_ERR_ARG_COUNT2,
|
||||||
|
cmd,
|
||||||
|
opts.status_cmd.to_wstr(),
|
||||||
|
0,
|
||||||
|
args.len()
|
||||||
|
));
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
if get_login() {
|
||||||
|
streams.out.append(wgettext!("This is a login shell\n"));
|
||||||
|
} else {
|
||||||
|
streams.out.append(wgettext!("This is not a login shell\n"));
|
||||||
|
}
|
||||||
|
let job_control_mode = match get_job_control_mode() {
|
||||||
|
job_control_t::interactive => wgettext!("Only on interactive jobs"),
|
||||||
|
job_control_t::none => wgettext!("Never"),
|
||||||
|
job_control_t::all => wgettext!("Always"),
|
||||||
|
};
|
||||||
|
streams
|
||||||
|
.out
|
||||||
|
.append(wgettext_fmt!("Job control: %ls\n", job_control_mode));
|
||||||
|
streams.out.append(parser.stack_trace().from_ffi());
|
||||||
|
}
|
||||||
|
STATUS_SET_JOB_CONTROL => {
|
||||||
|
let job_control_mode = match opts.new_job_control_mode {
|
||||||
|
Some(j) => {
|
||||||
|
// Flag form used
|
||||||
|
if !args.is_empty() {
|
||||||
|
streams.err.append(wgettext_fmt!(
|
||||||
|
BUILTIN_ERR_ARG_COUNT2,
|
||||||
|
cmd,
|
||||||
|
opts.status_cmd.to_wstr(),
|
||||||
|
0,
|
||||||
|
args.len()
|
||||||
|
));
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
j
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
streams.err.append(wgettext_fmt!(
|
||||||
|
BUILTIN_ERR_ARG_COUNT2,
|
||||||
|
cmd,
|
||||||
|
opts.status_cmd.to_wstr(),
|
||||||
|
1,
|
||||||
|
args.len()
|
||||||
|
));
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
let Ok(new_mode)= args[0].try_into() else {
|
||||||
|
streams.err.append(wgettext_fmt!("%ls: Invalid job control mode '%ls'\n", cmd, args[0]));
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
};
|
||||||
|
new_mode
|
||||||
|
}
|
||||||
|
};
|
||||||
|
set_job_control_mode(job_control_mode);
|
||||||
|
}
|
||||||
|
STATUS_FEATURES => print_features(streams),
|
||||||
|
STATUS_TEST_FEATURE => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
streams.err.append(wgettext_fmt!(
|
||||||
|
BUILTIN_ERR_ARG_COUNT2,
|
||||||
|
cmd,
|
||||||
|
opts.status_cmd.to_wstr(),
|
||||||
|
1,
|
||||||
|
args.len()
|
||||||
|
));
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
use TestFeatureRetVal::*;
|
||||||
|
let mut retval = Some(TEST_FEATURE_NOT_RECOGNIZED as c_int);
|
||||||
|
for md in &feature_metadata() {
|
||||||
|
if md.name.from_ffi() == args[0] {
|
||||||
|
retval = match feature_test(md.flag) {
|
||||||
|
true => Some(TEST_FEATURE_ON as c_int),
|
||||||
|
false => Some(TEST_FEATURE_OFF as c_int),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
ref s => {
|
||||||
|
if !args.is_empty() {
|
||||||
|
streams.err.append(wgettext_fmt!(
|
||||||
|
BUILTIN_ERR_ARG_COUNT2,
|
||||||
|
cmd,
|
||||||
|
opts.status_cmd.to_wstr(),
|
||||||
|
0,
|
||||||
|
args.len()
|
||||||
|
));
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
match s {
|
||||||
|
STATUS_BASENAME | STATUS_DIRNAME | STATUS_FILENAME => {
|
||||||
|
let res = parser.current_filename_ffi().from_ffi();
|
||||||
|
let f = match (res.is_empty(), opts.status_cmd) {
|
||||||
|
(false, STATUS_DIRNAME) => wdirname(res),
|
||||||
|
(false, STATUS_BASENAME) => wbasename(res),
|
||||||
|
(true, _) => wgettext!("Standard input").to_owned(),
|
||||||
|
(false, _) => res,
|
||||||
|
};
|
||||||
|
streams.out.append(wgettext_fmt!("%ls\n", f));
|
||||||
|
}
|
||||||
|
STATUS_FUNCTION => {
|
||||||
|
let f = match parser.get_func_name(opts.level) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => wgettext!("Not a function").to_owned(),
|
||||||
|
};
|
||||||
|
streams.out.append(wgettext_fmt!("%ls\n", f));
|
||||||
|
}
|
||||||
|
STATUS_LINE_NUMBER => {
|
||||||
|
// TBD is how to interpret the level argument when fetching the line number.
|
||||||
|
// See issue #4161.
|
||||||
|
// streams.out.append_format(L"%d\n", parser.get_lineno(opts.level));
|
||||||
|
streams
|
||||||
|
.out
|
||||||
|
.append(wgettext_fmt!("%d\n", parser.get_lineno().0));
|
||||||
|
}
|
||||||
|
STATUS_IS_INTERACTIVE => {
|
||||||
|
if is_interactive_session() {
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
} else {
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_IS_COMMAND_SUB => {
|
||||||
|
if parser.libdata_pod().is_subshell {
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
} else {
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_IS_BLOCK => {
|
||||||
|
if parser.is_block() {
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
} else {
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_IS_BREAKPOINT => {
|
||||||
|
if parser.is_breakpoint() {
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
} else {
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_IS_LOGIN => {
|
||||||
|
if get_login() {
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
} else {
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_IS_FULL_JOB_CTRL => {
|
||||||
|
if get_job_control_mode() == job_control_t::all {
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
} else {
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_IS_INTERACTIVE_JOB_CTRL => {
|
||||||
|
if get_job_control_mode() == job_control_t::interactive {
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
} else {
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_IS_NO_JOB_CTRL => {
|
||||||
|
if get_job_control_mode() == job_control_t::none {
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
} else {
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_STACK_TRACE => {
|
||||||
|
streams.out.append(parser.stack_trace().from_ffi());
|
||||||
|
}
|
||||||
|
STATUS_CURRENT_CMD => {
|
||||||
|
let var = parser.pin().libdata().get_status_vars_command().from_ffi();
|
||||||
|
if !var.is_empty() {
|
||||||
|
streams.out.append(var);
|
||||||
|
} else {
|
||||||
|
// FIXME: C++ used `program_name` here, no clue where it's from
|
||||||
|
streams.out.append(L!("fish"));
|
||||||
|
}
|
||||||
|
streams.out.append1('\n');
|
||||||
|
}
|
||||||
|
STATUS_CURRENT_COMMANDLINE => {
|
||||||
|
let var = parser
|
||||||
|
.pin()
|
||||||
|
.libdata()
|
||||||
|
.get_status_vars_commandline()
|
||||||
|
.from_ffi();
|
||||||
|
streams.out.append(var);
|
||||||
|
streams.out.append1('\n');
|
||||||
|
}
|
||||||
|
STATUS_FISH_PATH => {
|
||||||
|
let path = get_executable_path("fish");
|
||||||
|
if path.is_empty() {
|
||||||
|
streams.err.append(wgettext_fmt!(
|
||||||
|
"%ls: Could not get executable path: '%s'\n",
|
||||||
|
cmd,
|
||||||
|
Errno::last().to_string()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if path.is_absolute() {
|
||||||
|
let path = str2wcstring(path.as_os_str().as_bytes());
|
||||||
|
// This is an absoulte path, we can canonicalize it
|
||||||
|
let real = match wrealpath(&path) {
|
||||||
|
Some(p) if waccess(&p, F_OK) == 0 => p,
|
||||||
|
// realpath did not work, just append the path
|
||||||
|
// - maybe this was obtained via $PATH?
|
||||||
|
_ => path,
|
||||||
|
};
|
||||||
|
|
||||||
|
streams.out.append(real);
|
||||||
|
streams.out.append1('\n');
|
||||||
|
} else {
|
||||||
|
// This is a relative path, we can't canonicalize it
|
||||||
|
let path = str2wcstring(path.as_os_str().as_bytes());
|
||||||
|
streams.out.append(path);
|
||||||
|
streams.out.append1('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
STATUS_UNDEF | STATUS_SET_JOB_CONTROL | STATUS_FEATURES | STATUS_TEST_FEATURE => {
|
||||||
|
unreachable!("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
|
@ -1728,7 +1728,7 @@ pub fn valid_var_name(s: &wstr) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the absolute path to the fish executable itself
|
/// Get the absolute path to the fish executable itself
|
||||||
fn get_executable_path(argv0: &str) -> PathBuf {
|
pub fn get_executable_path(argv0: &str) -> PathBuf {
|
||||||
std::env::current_exe().unwrap_or_else(|_| PathBuf::from_str(argv0).unwrap())
|
std::env::current_exe().unwrap_or_else(|_| PathBuf::from_str(argv0).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,10 @@ include_cpp! {
|
||||||
generate!("parser_t")
|
generate!("parser_t")
|
||||||
|
|
||||||
generate!("job_t")
|
generate!("job_t")
|
||||||
|
generate!("job_control_t")
|
||||||
|
generate!("get_job_control_mode")
|
||||||
|
generate!("set_job_control_mode")
|
||||||
|
generate!("get_login")
|
||||||
generate!("process_t")
|
generate!("process_t")
|
||||||
generate!("library_data_t")
|
generate!("library_data_t")
|
||||||
generate_pod!("library_data_pod_t")
|
generate_pod!("library_data_pod_t")
|
||||||
|
@ -400,3 +404,16 @@ impl core::convert::From<void_ptr> for *const autocxx::c_void {
|
||||||
value.0 as *const _
|
value.0 as *const _
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&wstr> for job_control_t {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: &wstr) -> Result<Self, Self::Error> {
|
||||||
|
match value.to_string().as_str() {
|
||||||
|
"full" => Ok(job_control_t::all),
|
||||||
|
"interactive" => Ok(job_control_t::interactive),
|
||||||
|
"none" => Ok(job_control_t::none),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -642,7 +642,7 @@ fn create_directory(d: &wstr) -> bool {
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if errno().0 == ENOENT {
|
if errno().0 == ENOENT {
|
||||||
let dir = wdirname(d.to_owned());
|
let dir = wdirname(d);
|
||||||
if create_directory(&dir) && wmkdir(d, 0o700) == 0 {
|
if create_directory(&dir) && wmkdir(d, 0o700) == 0 {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ pub mod errors;
|
||||||
pub mod fileid;
|
pub mod fileid;
|
||||||
pub mod gettext;
|
pub mod gettext;
|
||||||
pub mod printf;
|
pub mod printf;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
pub mod wcstod;
|
pub mod wcstod;
|
||||||
pub mod wcstoi;
|
pub mod wcstoi;
|
||||||
|
|
||||||
|
@ -321,22 +323,24 @@ pub fn path_normalize_for_cd(wd: &wstr, path: &wstr) -> WString {
|
||||||
|
|
||||||
/// Wide character version of dirname().
|
/// Wide character version of dirname().
|
||||||
#[widestrs]
|
#[widestrs]
|
||||||
pub fn wdirname(mut path: WString) -> WString {
|
pub fn wdirname(path: impl AsRef<wstr>) -> WString {
|
||||||
|
let path = path.as_ref();
|
||||||
// Do not use system-provided dirname (#7837).
|
// Do not use system-provided dirname (#7837).
|
||||||
// On Mac it's not thread safe, and will error for paths exceeding PATH_MAX.
|
// On Mac it's not thread safe, and will error for paths exceeding PATH_MAX.
|
||||||
// This follows OpenGroup dirname recipe.
|
// This follows OpenGroup dirname recipe.
|
||||||
|
|
||||||
// 1: Double-slash stays.
|
// 1: Double-slash stays.
|
||||||
if path == "//"L {
|
if path == "//"L {
|
||||||
return path;
|
return path.to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2: All slashes => return slash.
|
// 2: All slashes => return slash.
|
||||||
if !path.is_empty() && path.chars().find(|c| *c == '/').is_none() {
|
if !path.is_empty() && path.chars().find(|&c| c != '/').is_none() {
|
||||||
return "/"L.to_owned();
|
return "/"L.to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3: Trim trailing slashes.
|
// 3: Trim trailing slashes.
|
||||||
|
let mut path = path.to_owned();
|
||||||
while path.as_char_slice().last() == Some(&'/') {
|
while path.as_char_slice().last() == Some(&'/') {
|
||||||
path.pop();
|
path.pop();
|
||||||
}
|
}
|
||||||
|
@ -347,13 +351,17 @@ pub fn wdirname(mut path: WString) -> WString {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 5: Remove trailing non-slashes.
|
// 5: Remove trailing non-slashes.
|
||||||
path.truncate(last_slash + 1);
|
|
||||||
|
|
||||||
// 6: Skip as permitted.
|
// 6: Skip as permitted.
|
||||||
// 7: Remove trailing slashes again.
|
// 7: Remove trailing slashes again.
|
||||||
while path.as_char_slice().last() == Some(&'/') {
|
path = path
|
||||||
path.pop();
|
.chars()
|
||||||
}
|
.rev()
|
||||||
|
.skip(last_slash + 1)
|
||||||
|
.skip_while(|&c| c == '/')
|
||||||
|
.collect::<WString>()
|
||||||
|
.chars()
|
||||||
|
.rev()
|
||||||
|
.collect();
|
||||||
|
|
||||||
// 8: Empty => return slash.
|
// 8: Empty => return slash.
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
|
@ -364,7 +372,8 @@ pub fn wdirname(mut path: WString) -> WString {
|
||||||
|
|
||||||
/// Wide character version of basename().
|
/// Wide character version of basename().
|
||||||
#[widestrs]
|
#[widestrs]
|
||||||
pub fn wbasename(mut path: WString) -> WString {
|
pub fn wbasename(path: impl AsRef<wstr>) -> WString {
|
||||||
|
let path = path.as_ref();
|
||||||
// This follows OpenGroup basename recipe.
|
// This follows OpenGroup basename recipe.
|
||||||
// 1: empty => allowed to return ".". This is what system impls do.
|
// 1: empty => allowed to return ".". This is what system impls do.
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
|
@ -373,21 +382,20 @@ pub fn wbasename(mut path: WString) -> WString {
|
||||||
|
|
||||||
// 2: Skip as permitted.
|
// 2: Skip as permitted.
|
||||||
// 3: All slashes => return slash.
|
// 3: All slashes => return slash.
|
||||||
if !path.is_empty() && path.chars().find(|c| *c == '/').is_none() {
|
if !path.is_empty() && path.chars().find(|&c| c != '/').is_none() {
|
||||||
return "/"L.to_owned();
|
return "/"L.to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4: Remove trailing slashes.
|
// 4: Remove trailing slashes.
|
||||||
// while (!path.is_empty() && path.back() == '/') path.pop_back();
|
|
||||||
while path.as_char_slice().last() == Some(&'/') {
|
|
||||||
path.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5: Remove up to and including last slash.
|
// 5: Remove up to and including last slash.
|
||||||
if let Some(last_slash) = path.chars().rev().position(|c| c == '/') {
|
path.chars()
|
||||||
path.truncate(last_slash + 1);
|
.rev()
|
||||||
};
|
.skip_while(|&c| c == '/')
|
||||||
path
|
.take_while(|&c| c != '/')
|
||||||
|
.collect::<WString>()
|
||||||
|
.chars()
|
||||||
|
.rev()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wide character version of mkdir.
|
/// Wide character version of mkdir.
|
||||||
|
|
60
fish-rust/src/wutil/tests.rs
Normal file
60
fish-rust/src/wutil/tests.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use super::*;
|
||||||
|
use libc::PATH_MAX;
|
||||||
|
|
||||||
|
macro_rules! test_cases_wdirname_wbasename {
|
||||||
|
($($name:ident: $test:expr),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let (path, dir, base) = $test;
|
||||||
|
let actual = wdirname(WString::from(path));
|
||||||
|
assert_eq!(actual, WString::from(dir), "Wrong dirname for {:?}", path);
|
||||||
|
let actual = wbasename(WString::from(path));
|
||||||
|
assert_eq!(actual, WString::from(base), "Wrong basename for {:?}", path);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to return a string whose length greatly exceeds PATH_MAX.
|
||||||
|
fn overlong_path() -> WString {
|
||||||
|
let mut longpath = WString::with_capacity((PATH_MAX * 2 + 10) as usize);
|
||||||
|
while longpath.len() < (PATH_MAX * 2) as usize {
|
||||||
|
longpath.push_str("/overlong");
|
||||||
|
}
|
||||||
|
return longpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_cases_wdirname_wbasename! {
|
||||||
|
wdirname_wbasename_test_1: ("", ".", "."),
|
||||||
|
wdirname_wbasename_test_2: ("foo//", ".", "foo"),
|
||||||
|
wdirname_wbasename_test_3: ("foo//////", ".", "foo"),
|
||||||
|
wdirname_wbasename_test_4: ("/////foo", "/", "foo"),
|
||||||
|
wdirname_wbasename_test_5: ("/////foo", "/", "foo"),
|
||||||
|
wdirname_wbasename_test_6: ("//foo/////bar", "//foo", "bar"),
|
||||||
|
wdirname_wbasename_test_7: ("foo/////bar", "foo", "bar"),
|
||||||
|
// Examples given in XPG4.2.
|
||||||
|
wdirname_wbasename_test_8: ("/usr/lib", "/usr", "lib"),
|
||||||
|
wdirname_wbasename_test_9: ("usr", ".", "usr"),
|
||||||
|
wdirname_wbasename_test_10: ("/", "/", "/"),
|
||||||
|
wdirname_wbasename_test_11: (".", ".", "."),
|
||||||
|
wdirname_wbasename_test_12: ("..", ".", ".."),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures strings which greatly exceed PATH_MAX still work (#7837).
|
||||||
|
#[test]
|
||||||
|
fn test_overlong_wdirname_wbasename() {
|
||||||
|
let path = overlong_path();
|
||||||
|
let dir = {
|
||||||
|
let mut longpath_dir = path.clone();
|
||||||
|
let last_slash = longpath_dir.chars().rev().position(|c| c == '/').unwrap();
|
||||||
|
longpath_dir.truncate(longpath_dir.len() - last_slash - 1);
|
||||||
|
longpath_dir
|
||||||
|
};
|
||||||
|
let base = "overlong";
|
||||||
|
|
||||||
|
let actual = wdirname(&path);
|
||||||
|
assert_eq!(actual, dir, "Wrong dirname for {:?}", path);
|
||||||
|
let actual = wbasename(&path);
|
||||||
|
assert_eq!(actual, base, "Wrong basename for {:?}", path);
|
||||||
|
}
|
|
@ -394,7 +394,7 @@ static constexpr builtin_data_t builtin_datas[] = {
|
||||||
{L"set", &builtin_set, N_(L"Handle environment variables")},
|
{L"set", &builtin_set, N_(L"Handle environment variables")},
|
||||||
{L"set_color", &implemented_in_rust, N_(L"Set the terminal color")},
|
{L"set_color", &implemented_in_rust, N_(L"Set the terminal color")},
|
||||||
{L"source", &builtin_source, N_(L"Evaluate contents of file")},
|
{L"source", &builtin_source, N_(L"Evaluate contents of file")},
|
||||||
{L"status", &builtin_status, N_(L"Return status information about fish")},
|
{L"status", &implemented_in_rust, N_(L"Return status information about fish")},
|
||||||
{L"string", &builtin_string, N_(L"Manipulate strings")},
|
{L"string", &builtin_string, N_(L"Manipulate strings")},
|
||||||
{L"switch", &builtin_generic, N_(L"Conditionally run blocks of code")},
|
{L"switch", &builtin_generic, N_(L"Conditionally run blocks of code")},
|
||||||
{L"test", &implemented_in_rust, N_(L"Test a condition")},
|
{L"test", &implemented_in_rust, N_(L"Test a condition")},
|
||||||
|
@ -565,6 +565,9 @@ static maybe_t<RustBuiltin> try_get_rust_builtin(const wcstring &cmd) {
|
||||||
if (cmd == L"set_color") {
|
if (cmd == L"set_color") {
|
||||||
return RustBuiltin::SetColor;
|
return RustBuiltin::SetColor;
|
||||||
}
|
}
|
||||||
|
if (cmd == L"status") {
|
||||||
|
return RustBuiltin::Status;
|
||||||
|
}
|
||||||
if (cmd == L"test" || cmd == L"[") {
|
if (cmd == L"test" || cmd == L"[") {
|
||||||
return RustBuiltin::Test;
|
return RustBuiltin::Test;
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,7 @@ enum class RustBuiltin : int32_t {
|
||||||
Realpath,
|
Realpath,
|
||||||
Return,
|
Return,
|
||||||
SetColor,
|
SetColor,
|
||||||
|
Status,
|
||||||
Test,
|
Test,
|
||||||
Type,
|
Type,
|
||||||
Wait,
|
Wait,
|
||||||
|
|
|
@ -412,6 +412,16 @@ filename_ref_t parser_t::current_filename() const {
|
||||||
return libdata().current_filename;
|
return libdata().current_filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FFI glue
|
||||||
|
wcstring parser_t::current_filename_ffi() const {
|
||||||
|
auto filename = current_filename();
|
||||||
|
if (filename) {
|
||||||
|
return wcstring(*filename);
|
||||||
|
} else {
|
||||||
|
return wcstring();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool parser_t::function_stack_is_overflowing() const {
|
bool parser_t::function_stack_is_overflowing() const {
|
||||||
// We are interested in whether the count of functions on the stack exceeds
|
// We are interested in whether the count of functions on the stack exceeds
|
||||||
// FISH_MAX_STACK_DEPTH. We don't separately track the number of functions, but we can have a
|
// FISH_MAX_STACK_DEPTH. We don't separately track the number of functions, but we can have a
|
||||||
|
|
|
@ -224,6 +224,10 @@ struct library_data_t : public library_data_pod_t {
|
||||||
/// Used to get the full text of the current job for `status current-commandline`.
|
/// Used to get the full text of the current job for `status current-commandline`.
|
||||||
wcstring commandline;
|
wcstring commandline;
|
||||||
} status_vars;
|
} status_vars;
|
||||||
|
|
||||||
|
public:
|
||||||
|
wcstring get_status_vars_command() const { return status_vars.command; }
|
||||||
|
wcstring get_status_vars_commandline() const { return status_vars.commandline; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The result of parser_t::eval family.
|
/// The result of parser_t::eval family.
|
||||||
|
@ -468,6 +472,7 @@ class parser_t : public std::enable_shared_from_this<parser_t> {
|
||||||
/// reader_current_filename, e.g. if we are evaluating a function defined in a different file
|
/// reader_current_filename, e.g. if we are evaluating a function defined in a different file
|
||||||
/// than the one currently read.
|
/// than the one currently read.
|
||||||
filename_ref_t current_filename() const;
|
filename_ref_t current_filename() const;
|
||||||
|
wcstring current_filename_ffi() const;
|
||||||
|
|
||||||
/// Return if we are interactive, which means we are executing a command that the user typed in
|
/// Return if we are interactive, which means we are executing a command that the user typed in
|
||||||
/// (and not, say, a prompt).
|
/// (and not, say, a prompt).
|
||||||
|
|
Loading…
Reference in a new issue