builtins: Rewrite pwd in Rust

Closes #9625.
This commit is contained in:
Victor Song 2023-03-01 00:05:27 -05:00 committed by Mahmoud Al-Qudsi
parent 6809a8dfbc
commit 77fe9933e2
15 changed files with 141 additions and 117 deletions

View file

@ -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

1
fish-rust/Cargo.lock generated
View file

@ -348,6 +348,7 @@ version = "0.1.0"
dependencies = [
"autocxx",
"autocxx-build",
"bitflags",
"cxx",
"cxx-build",
"cxx-gen",

View file

@ -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}

View file

@ -5,7 +5,7 @@ use crate::builtins::shared::{
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<c_int> {
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

View file

@ -7,6 +7,7 @@ pub mod contains;
pub mod echo;
pub mod emit;
pub mod exit;
pub mod pwd;
pub mod random;
pub mod r#return;
pub mod wait;

View file

@ -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<c_int> {
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;
}

View file

@ -37,6 +37,8 @@ pub const BUILTIN_ERR_TOO_MANY_ARGUMENTS: &str = "%ls: too many arguments\n";
/// 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<c_int> = 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),

View file

@ -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<EnvMode> for c_int {
fn from(val: EnvMode) -> Self {
c_int(i32::from(val.bits()))
}
}
}
/// Return values for `env_stack_t::set()`.

View file

@ -103,6 +103,7 @@ include_cpp! {
generate!("io_chain_t")
generate!("termsize_container_t")
generate!("env_var_t")
}
impl parser_t {

View file

@ -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<RustBuiltin> 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;
}

View file

@ -116,6 +116,7 @@ enum RustBuiltin : int32_t {
Echo,
Emit,
Exit,
Pwd,
Random,
Return,
Wait,

View file

@ -1,78 +0,0 @@
// Implementation of the pwd builtin.
#include "config.h" // IWYU pragma: keep
#include "pwd.h"
#include <cerrno>
#include <cstring>
#include <string>
#include <utility>
#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<int> 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;
}

View file

@ -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<int> builtin_pwd(parser_t &parser, io_streams_t &streams, const wchar_t **argv);
#endif

View file

@ -1476,6 +1476,13 @@ const std::shared_ptr<env_stack_t> &env_stack_t::principal_ref() {
new env_stack_t(env_stack_impl_t::create())};
return s_principal;
}
__attribute__((unused)) std::unique_ptr<env_var_t> 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<env_var_t>()
: std::unique_ptr<env_var_t>(new env_var_t(variable.value()));
}
env_stack_t::~env_stack_t() = default;

View file

@ -290,6 +290,9 @@ class env_stack_t final : public environment_t {
/// \return a list of events for changed variables.
std::vector<rust::Box<Event>> universal_sync(bool always);
__attribute__((unused)) std::unique_ptr<env_var_t> 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<env_stack_t> &principal_ref();
static env_stack_t &principal() { return *principal_ref(); }