diff --git a/CMakeLists.txt b/CMakeLists.txt index ace81bac0..c5504a2e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,7 @@ set(FISH_BUILTIN_SRCS src/builtins/eval.cpp src/builtins/fg.cpp src/builtins/function.cpp src/builtins/functions.cpp src/builtins/history.cpp src/builtins/jobs.cpp src/builtins/math.cpp src/builtins/printf.cpp src/builtins/path.cpp - src/builtins/pwd.cpp src/builtins/read.cpp + src/builtins/read.cpp src/builtins/realpath.cpp src/builtins/set.cpp src/builtins/set_color.cpp src/builtins/source.cpp src/builtins/status.cpp src/builtins/string.cpp src/builtins/test.cpp src/builtins/type.cpp src/builtins/ulimit.cpp diff --git a/fish-rust/Cargo.lock b/fish-rust/Cargo.lock index 5eb4a0019..d031ea335 100644 --- a/fish-rust/Cargo.lock +++ b/fish-rust/Cargo.lock @@ -348,6 +348,7 @@ version = "0.1.0" dependencies = [ "autocxx", "autocxx-build", + "bitflags", "cxx", "cxx-build", "cxx-gen", diff --git a/fish-rust/Cargo.toml b/fish-rust/Cargo.toml index 57ccdadac..a37181619 100644 --- a/fish-rust/Cargo.toml +++ b/fish-rust/Cargo.toml @@ -9,6 +9,7 @@ rust-version = "1.67" widestring-suffix = { path = "./widestring-suffix/" } autocxx = "0.23.1" +bitflags = "1.3.2" cxx = "1.0" errno = "0.2.8" inventory = { version = "0.3.3", optional = true} diff --git a/fish-rust/src/builtins/abbr.rs b/fish-rust/src/builtins/abbr.rs index bd4ac9d7f..a075df2f3 100644 --- a/fish-rust/src/builtins/abbr.rs +++ b/fish-rust/src/builtins/abbr.rs @@ -5,7 +5,7 @@ STATUS_CMD_OK, STATUS_INVALID_ARGS, }; use crate::common::{escape_string, valid_func_name, EscapeStringStyle}; -use crate::env::flags::ENV_UNIVERSAL; +use crate::env::flags::EnvMode; use crate::env::status::{ENV_NOT_FOUND, ENV_OK}; use crate::ffi::{self, parser_t}; use crate::re::regex_make_anchored; @@ -417,7 +417,7 @@ fn abbr_erase(opts: &Options, parser: &mut parser_t) -> Option { let esc_src = escape_string(arg, EscapeStringStyle::Script(Default::default())); if !esc_src.is_empty() { let var_name = WString::from_str("_fish_abbr_") + esc_src.as_utfstr(); - let ret = parser.remove_var(&var_name, ENV_UNIVERSAL); + let ret = parser.remove_var(&var_name, EnvMode::UNIVERSAL.into()); if ret == autocxx::c_int(ENV_OK) { result = STATUS_CMD_OK diff --git a/fish-rust/src/builtins/mod.rs b/fish-rust/src/builtins/mod.rs index 9d2b3265e..fc889cfee 100644 --- a/fish-rust/src/builtins/mod.rs +++ b/fish-rust/src/builtins/mod.rs @@ -7,6 +7,7 @@ pub mod echo; pub mod emit; pub mod exit; +pub mod pwd; pub mod random; pub mod r#return; pub mod wait; diff --git a/fish-rust/src/builtins/pwd.rs b/fish-rust/src/builtins/pwd.rs new file mode 100644 index 000000000..09a9f96c1 --- /dev/null +++ b/fish-rust/src/builtins/pwd.rs @@ -0,0 +1,80 @@ +//! Implementation of the pwd builtin. +use errno::errno; +use libc::c_int; + +use crate::{ + builtins::shared::{io_streams_t, BUILTIN_ERR_ARG_COUNT1}, + env::flags::EnvMode, + ffi::parser_t, + wchar::{wstr, WString, L}, + wchar_ffi::{WCharFromFFI, WCharToFFI}, + wgetopt::{wgetopter_t, wopt, woption, woption_argument_t::no_argument}, + wutil::{wgettext_fmt, wrealpath}, +}; + +use super::shared::{ + builtin_print_help, builtin_unknown_option, STATUS_CMD_ERROR, STATUS_CMD_OK, + STATUS_INVALID_ARGS, +}; + +// The pwd builtin. Respect -P to resolve symbolic links. Respect -L to not do that (the default). +const short_options: &wstr = L!("LPh"); +const long_options: &[woption] = &[ + wopt(L!("help"), no_argument, 'h'), + wopt(L!("logical"), no_argument, 'L'), + wopt(L!("physical"), no_argument, 'P'), +]; + +pub fn pwd(parser: &mut parser_t, streams: &mut io_streams_t, argv: &mut [&wstr]) -> Option { + let cmd = argv[0]; + let argc = argv.len(); + let mut resolve_symlinks = false; + let mut w = wgetopter_t::new(short_options, long_options, argv); + while let Some(opt) = w.wgetopt_long() { + match opt { + 'L' => resolve_symlinks = false, + 'P' => resolve_symlinks = true, + 'h' => { + builtin_print_help(parser, streams, cmd); + return STATUS_CMD_OK; + } + '?' => { + builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], false); + return STATUS_INVALID_ARGS; + } + _ => panic!("unexpected retval from wgetopt_long"), + } + } + + if w.woptind != argc { + streams + .err + .append(wgettext_fmt!(BUILTIN_ERR_ARG_COUNT1, cmd, 0, argc - 1)); + return STATUS_INVALID_ARGS; + } + + let mut pwd = WString::new(); + let tmp = parser + .vars1() + .get_or_null(&L!("PWD").to_ffi(), EnvMode::DEFAULT.bits()); + if !tmp.is_null() { + pwd = tmp.as_string().from_ffi(); + } + if resolve_symlinks { + if let Some(real_pwd) = wrealpath(&pwd) { + pwd = real_pwd; + } else { + streams.err.append(wgettext_fmt!( + "%ls: realpath failed: %s\n", + cmd, + errno().to_string() + )); + return STATUS_CMD_ERROR; + } + } + if pwd.is_empty() { + return STATUS_CMD_ERROR; + } + streams.out.append(pwd + L!("\n")); + return STATUS_CMD_OK; +} diff --git a/fish-rust/src/builtins/shared.rs b/fish-rust/src/builtins/shared.rs index 9f6719a56..ec2ac45a9 100644 --- a/fish-rust/src/builtins/shared.rs +++ b/fish-rust/src/builtins/shared.rs @@ -37,6 +37,8 @@ impl Vec {} /// Error message when integer expected pub const BUILTIN_ERR_NOT_NUMBER: &str = "%ls: %ls: invalid integer\n"; +pub const BUILTIN_ERR_ARG_COUNT1: &str = "%ls: expected %d arguments; got %d\n"; + /// A handy return value for successful builtins. pub const STATUS_CMD_OK: Option = Some(0); @@ -125,6 +127,7 @@ pub fn run_builtin( RustBuiltin::Echo => super::echo::echo(parser, streams, args), RustBuiltin::Emit => super::emit::emit(parser, streams, args), RustBuiltin::Exit => super::exit::exit(parser, streams, args), + RustBuiltin::Pwd => super::pwd::pwd(parser, streams, args), RustBuiltin::Random => super::random::random(parser, streams, args), RustBuiltin::Return => super::r#return::r#return(parser, streams, args), RustBuiltin::Wait => wait::wait(parser, streams, args), diff --git a/fish-rust/src/env.rs b/fish-rust/src/env.rs index 5b88741fb..38a3b18bf 100644 --- a/fish-rust/src/env.rs +++ b/fish-rust/src/env.rs @@ -1,30 +1,43 @@ /// Flags that may be passed as the 'mode' in env_stack_t::set() / environment_t::get(). pub mod flags { use autocxx::c_int; + use bitflags::bitflags; - /// Default mode. Used with `env_stack_t::get()` to indicate the caller doesn't care what scope - /// the var is in or whether it is exported or unexported. - pub const ENV_DEFAULT: c_int = c_int(0); - /// Flag for local (to the current block) variable. - pub const ENV_LOCAL: c_int = c_int(1 << 0); - pub const ENV_FUNCTION: c_int = c_int(1 << 1); - /// Flag for global variable. - pub const ENV_GLOBAL: c_int = c_int(1 << 2); - /// Flag for universal variable. - pub const ENV_UNIVERSAL: c_int = c_int(1 << 3); - /// Flag for exported (to commands) variable. - pub const ENV_EXPORT: c_int = c_int(1 << 4); - /// Flag for unexported variable. - pub const ENV_UNEXPORT: c_int = c_int(1 << 5); - /// Flag to mark a variable as a path variable. - pub const ENV_PATHVAR: c_int = c_int(1 << 6); - /// Flag to unmark a variable as a path variable. - pub const ENV_UNPATHVAR: c_int = c_int(1 << 7); - /// Flag for variable update request from the user. All variable changes that are made directly - /// by the user, such as those from the `read` and `set` builtin must have this flag set. It - /// serves one purpose: to indicate that an error should be returned if the user is attempting - /// to modify a var that should not be modified by direct user action; e.g., a read-only var. - pub const ENV_USER: c_int = c_int(1 << 8); + bitflags! { + /// Flags that may be passed as the 'mode' in env_stack_t::set() / environment_t::get(). + #[repr(C)] + pub struct EnvMode: u16 { + /// Default mode. Used with `env_stack_t::get()` to indicate the caller doesn't care what scope + /// the var is in or whether it is exported or unexported. + const DEFAULT = 0; + /// Flag for local (to the current block) variable. + const LOCAL = 1 << 0; + const FUNCTION = 1 << 1; + /// Flag for global variable. + const GLOBAL = 1 << 2; + /// Flag for universal variable. + const UNIVERSAL = 1 << 3; + /// Flag for exported (to commands) variable. + const EXPORT = 1 << 4; + /// Flag for unexported variable. + const UNEXPORT = 1 << 5; + /// Flag to mark a variable as a path variable. + const PATHVAR = 1 << 6; + /// Flag to unmark a variable as a path variable. + const UNPATHVAR = 1 << 7; + /// Flag for variable update request from the user. All variable changes that are made directly + /// by the user, such as those from the `read` and `set` builtin must have this flag set. It + /// serves one purpose: to indicate that an error should be returned if the user is attempting + /// to modify a var that should not be modified by direct user action; e.g., a read-only var. + const USER = 1 << 8; + } + } + + impl From for c_int { + fn from(val: EnvMode) -> Self { + c_int(i32::from(val.bits())) + } + } } /// Return values for `env_stack_t::set()`. diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index 9c340954b..42903ede9 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -103,6 +103,7 @@ generate!("io_chain_t") generate!("termsize_container_t") + generate!("env_var_t") } impl parser_t { diff --git a/src/builtin.cpp b/src/builtin.cpp index bca191139..9673912db 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -45,7 +45,6 @@ #include "builtins/math.h" #include "builtins/path.h" #include "builtins/printf.h" -#include "builtins/pwd.h" #include "builtins/read.h" #include "builtins/realpath.h" #include "builtins/set.h" @@ -395,7 +394,7 @@ static constexpr builtin_data_t builtin_datas[] = { {L"or", &builtin_generic, N_(L"Execute command if previous command failed")}, {L"path", &builtin_path, N_(L"Handle paths")}, {L"printf", &builtin_printf, N_(L"Prints formatted text")}, - {L"pwd", &builtin_pwd, N_(L"Print the working directory")}, + {L"pwd", &implemented_in_rust, N_(L"Print the working directory")}, {L"random", &implemented_in_rust, N_(L"Generate random number")}, {L"read", &builtin_read, N_(L"Read a line of input into variables")}, {L"realpath", &builtin_realpath, N_(L"Show absolute path sans symlinks")}, @@ -541,6 +540,9 @@ static maybe_t try_get_rust_builtin(const wcstring &cmd) { if (cmd == L"exit") { return RustBuiltin::Exit; } + if (cmd == L"pwd") { + return RustBuiltin::Pwd; + } if (cmd == L"random") { return RustBuiltin::Random; } diff --git a/src/builtin.h b/src/builtin.h index 5054fa770..11a987412 100644 --- a/src/builtin.h +++ b/src/builtin.h @@ -116,6 +116,7 @@ enum RustBuiltin : int32_t { Echo, Emit, Exit, + Pwd, Random, Return, Wait, diff --git a/src/builtins/pwd.cpp b/src/builtins/pwd.cpp deleted file mode 100644 index 664175276..000000000 --- a/src/builtins/pwd.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Implementation of the pwd builtin. -#include "config.h" // IWYU pragma: keep - -#include "pwd.h" - -#include -#include -#include -#include - -#include "../builtin.h" -#include "../common.h" -#include "../env.h" -#include "../fallback.h" // IWYU pragma: keep -#include "../io.h" -#include "../maybe.h" -#include "../parser.h" -#include "../wgetopt.h" -#include "../wutil.h" // IWYU pragma: keep - -/// The pwd builtin. Respect -P to resolve symbolic links. Respect -L to not do that (the default). -static const wchar_t *const short_options = L"LPh"; -static const struct woption long_options[] = {{L"help", no_argument, 'h'}, - {L"logical", no_argument, 'L'}, - {L"physical", no_argument, 'P'}, - {}}; -maybe_t builtin_pwd(parser_t &parser, io_streams_t &streams, const wchar_t **argv) { - UNUSED(parser); - const wchar_t *cmd = argv[0]; - int argc = builtin_count_args(argv); - bool resolve_symlinks = false; - wgetopter_t w; - int opt; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { - switch (opt) { - case 'L': - resolve_symlinks = false; - break; - case 'P': - resolve_symlinks = true; - break; - case 'h': - builtin_print_help(parser, streams, cmd); - return STATUS_CMD_OK; - case '?': { - builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - } - } - } - - if (w.woptind != argc) { - streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 0, argc - 1); - return STATUS_INVALID_ARGS; - } - - wcstring pwd; - if (auto tmp = parser.vars().get(L"PWD")) { - pwd = tmp->as_string(); - } - if (resolve_symlinks) { - if (auto real_pwd = wrealpath(pwd)) { - pwd = std::move(*real_pwd); - } else { - const char *error = std::strerror(errno); - streams.err.append_format(L"%ls: realpath failed: %s\n", cmd, error); - return STATUS_CMD_ERROR; - } - } - if (pwd.empty()) { - return STATUS_CMD_ERROR; - } - streams.out.append(pwd + L"\n"); - return STATUS_CMD_OK; -} diff --git a/src/builtins/pwd.h b/src/builtins/pwd.h deleted file mode 100644 index 124d834dc..000000000 --- a/src/builtins/pwd.h +++ /dev/null @@ -1,11 +0,0 @@ -// Prototypes for executing builtin_pwd function. -#ifndef FISH_BUILTIN_PWD_H -#define FISH_BUILTIN_PWD_H - -#include "../maybe.h" - -class parser_t; -struct io_streams_t; - -maybe_t builtin_pwd(parser_t &parser, io_streams_t &streams, const wchar_t **argv); -#endif diff --git a/src/env.cpp b/src/env.cpp index 0072df28c..5eef968cf 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1476,6 +1476,13 @@ const std::shared_ptr &env_stack_t::principal_ref() { new env_stack_t(env_stack_impl_t::create())}; return s_principal; } +__attribute__((unused)) std::unique_ptr env_stack_t::get_or_null( + wcstring const &key, env_mode_flags_t mode) const { + auto variable = get(key, mode); + return variable.missing_or_empty() + ? std::unique_ptr() + : std::unique_ptr(new env_var_t(variable.value())); +} env_stack_t::~env_stack_t() = default; diff --git a/src/env.h b/src/env.h index 7d9b1efd1..3f596804c 100644 --- a/src/env.h +++ b/src/env.h @@ -290,6 +290,9 @@ class env_stack_t final : public environment_t { /// \return a list of events for changed variables. std::vector> universal_sync(bool always); + __attribute__((unused)) std::unique_ptr get_or_null( + const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const; + // Compatibility hack; access the "environment stack" from back when there was just one. static const std::shared_ptr &principal_ref(); static env_stack_t &principal() { return *principal_ref(); }