mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-02-21 05:17:08 +08:00
Replace maybe_t::missing_or_empty with a more Rust-friendly helper
There are many places where we want to treat a missing variable the same as a variable with an empty value. In C++ we handle this by branching on maybe_t<env_var_t>::missing_or_empty(). If it returns false, we go on to access maybe_t<env_var_t>::value() aka operator*. In Rust, Environment::get() will return an Option<EnvVar>. We could define a MissingOrEmpty trait and implement it for Option<EnvVar>. However that will still leave us with ugly calls to Option::unwrap() (by convention Rust does use shorthands like *). Let's add a variable getter that returns none for empty variables.
This commit is contained in:
parent
82a797db9c
commit
1df64a4891
@ -43,8 +43,8 @@ maybe_t<int> builtin_cd(parser_t &parser, io_streams_t &streams, const wchar_t *
|
||||
if (argv[optind]) {
|
||||
dir_in = argv[optind];
|
||||
} else {
|
||||
auto maybe_dir_in = parser.vars().get(L"HOME");
|
||||
if (maybe_dir_in.missing_or_empty()) {
|
||||
auto maybe_dir_in = parser.vars().get_unless_empty(L"HOME");
|
||||
if (!maybe_dir_in) {
|
||||
streams.err.append_format(_(L"%ls: Could not find home directory\n"), cmd);
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
|
@ -566,8 +566,8 @@ maybe_t<int> builtin_read(parser_t &parser, io_streams_t &streams, const wchar_t
|
||||
}
|
||||
|
||||
if (!opts.have_delimiter) {
|
||||
auto ifs = parser.vars().get(L"IFS");
|
||||
if (!ifs.missing_or_empty()) opts.delimiter = ifs->as_string();
|
||||
auto ifs = parser.vars().get_unless_empty(L"IFS");
|
||||
if (ifs) opts.delimiter = ifs->as_string();
|
||||
}
|
||||
|
||||
if (opts.delimiter.empty()) {
|
||||
|
@ -455,8 +455,8 @@ static int builtin_set_list(const wchar_t *cmd, set_cmd_opts_t &opts, int argc,
|
||||
val += expand_escape_string(history->item_at_index(i).str());
|
||||
}
|
||||
} else {
|
||||
auto var = parser.vars().get(key, compute_scope(opts));
|
||||
if (!var.missing_or_empty()) {
|
||||
auto var = parser.vars().get_unless_empty(key, compute_scope(opts));
|
||||
if (var) {
|
||||
val = expand_escape_variable(*var);
|
||||
}
|
||||
}
|
||||
|
32
src/env.cpp
32
src/env.cpp
@ -178,9 +178,9 @@ environment_t::~environment_t() = default;
|
||||
wcstring environment_t::get_pwd_slash() const {
|
||||
// Return "/" if PWD is missing.
|
||||
// See https://github.com/fish-shell/fish-shell/issues/5080
|
||||
auto pwd_var = get(L"PWD");
|
||||
auto pwd_var = get_unless_empty(L"PWD");
|
||||
wcstring pwd;
|
||||
if (!pwd_var.missing_or_empty()) {
|
||||
if (pwd_var) {
|
||||
pwd = pwd_var->as_string();
|
||||
}
|
||||
if (!string_suffixes_string(L"/", pwd)) {
|
||||
@ -189,6 +189,16 @@ wcstring environment_t::get_pwd_slash() const {
|
||||
return pwd;
|
||||
}
|
||||
|
||||
maybe_t<env_var_t> environment_t::get_unless_empty(const wcstring &key,
|
||||
env_mode_flags_t mode) const {
|
||||
if (auto variable = this->get(key, mode)) {
|
||||
if (!variable->empty()) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
return none();
|
||||
}
|
||||
|
||||
std::unique_ptr<env_var_t> environment_t::get_or_null(wcstring const &key,
|
||||
env_mode_flags_t mode) const {
|
||||
auto variable = this->get(key, mode);
|
||||
@ -212,20 +222,20 @@ std::vector<wcstring> null_environment_t::get_names(env_mode_flags_t flags) cons
|
||||
/// Set up the USER and HOME variable.
|
||||
static void setup_user(env_stack_t &vars) {
|
||||
auto uid = geteuid();
|
||||
auto user_var = vars.get(L"USER");
|
||||
auto user_var = vars.get_unless_empty(L"USER");
|
||||
struct passwd userinfo;
|
||||
struct passwd *result;
|
||||
char buf[8192];
|
||||
|
||||
// If we have a $USER, we try to get the passwd entry for the name.
|
||||
// If that has the same UID that we use, we assume the data is correct.
|
||||
if (!user_var.missing_or_empty()) {
|
||||
if (user_var) {
|
||||
std::string unam_narrow = wcs2zstring(user_var->as_string());
|
||||
int retval = getpwnam_r(unam_narrow.c_str(), &userinfo, buf, sizeof(buf), &result);
|
||||
if (!retval && result) {
|
||||
if (result->pw_uid == uid) {
|
||||
// The uid matches but we still might need to set $HOME.
|
||||
if (vars.get(L"HOME").missing_or_empty()) {
|
||||
if (!vars.get_unless_empty(L"HOME")) {
|
||||
if (userinfo.pw_dir) {
|
||||
vars.set_one(L"HOME", ENV_GLOBAL | ENV_EXPORT,
|
||||
str2wcstring(userinfo.pw_dir));
|
||||
@ -246,7 +256,7 @@ static void setup_user(env_stack_t &vars) {
|
||||
vars.set_one(L"USER", ENV_GLOBAL | ENV_EXPORT, uname);
|
||||
// Only change $HOME if it's empty, so we allow e.g. `HOME=(mktemp -d)`.
|
||||
// This is okay with common `su` and `sudo` because they set $HOME.
|
||||
if (vars.get(L"HOME").missing_or_empty()) {
|
||||
if (!vars.get_unless_empty(L"HOME")) {
|
||||
if (userinfo.pw_dir) {
|
||||
vars.set_one(L"HOME", ENV_GLOBAL | ENV_EXPORT, str2wcstring(userinfo.pw_dir));
|
||||
} else {
|
||||
@ -255,7 +265,7 @@ static void setup_user(env_stack_t &vars) {
|
||||
vars.set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT);
|
||||
}
|
||||
}
|
||||
} else if (vars.get(L"HOME").missing_or_empty()) {
|
||||
} else if (!vars.get_unless_empty(L"HOME")) {
|
||||
// If $USER is empty as well (which we tried to set above), we can't get $HOME.
|
||||
vars.set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT);
|
||||
}
|
||||
@ -276,8 +286,8 @@ void misc_init() {
|
||||
/// Make sure the PATH variable contains something.
|
||||
static void setup_path() {
|
||||
auto &vars = env_stack_t::globals();
|
||||
const auto path = vars.get(L"PATH");
|
||||
if (path.missing_or_empty()) {
|
||||
const auto path = vars.get_unless_empty(L"PATH");
|
||||
if (!path) {
|
||||
#if defined(_CS_PATH)
|
||||
// _CS_PATH: colon-separated paths to find POSIX utilities
|
||||
std::string cspath;
|
||||
@ -419,9 +429,9 @@ void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_pa
|
||||
// Initialize termsize variables.
|
||||
environment_t &env_vars = vars;
|
||||
auto termsize = termsize_initialize_ffi(reinterpret_cast<const unsigned char *>(&env_vars));
|
||||
if (vars.get(L"COLUMNS").missing_or_empty())
|
||||
if (!vars.get_unless_empty(L"COLUMNS"))
|
||||
vars.set_one(L"COLUMNS", ENV_GLOBAL, to_string(termsize.width));
|
||||
if (vars.get(L"LINES").missing_or_empty())
|
||||
if (!vars.get_unless_empty(L"LINES"))
|
||||
vars.set_one(L"LINES", ENV_GLOBAL, to_string(termsize.height));
|
||||
|
||||
// Set fish_bind_mode to "default".
|
||||
|
@ -194,6 +194,8 @@ class environment_t {
|
||||
virtual std::vector<wcstring> get_names(env_mode_flags_t flags) const = 0;
|
||||
virtual ~environment_t();
|
||||
|
||||
maybe_t<env_var_t> get_unless_empty(const wcstring &key,
|
||||
env_mode_flags_t mode = ENV_DEFAULT) const;
|
||||
/// \return a environment variable as a unique pointer, or nullptr if none.
|
||||
std::unique_ptr<env_var_t> get_or_null(const wcstring &key,
|
||||
env_mode_flags_t mode = ENV_DEFAULT) const;
|
||||
|
@ -136,11 +136,11 @@ void env_dispatch_init(const environment_t &vars) {
|
||||
|
||||
/// Properly sets all timezone information.
|
||||
static void handle_timezone(const wchar_t *env_var_name, const environment_t &vars) {
|
||||
const auto var = vars.get(env_var_name, ENV_DEFAULT);
|
||||
const auto var = vars.get_unless_empty(env_var_name, ENV_DEFAULT);
|
||||
FLOGF(env_dispatch, L"handle_timezone() current timezone var: |%ls| => |%ls|", env_var_name,
|
||||
!var ? L"MISSING" : var->as_string().c_str());
|
||||
!var ? L"MISSING/EMPTY" : var->as_string().c_str());
|
||||
std::string name = wcs2zstring(env_var_name);
|
||||
if (var.missing_or_empty()) {
|
||||
if (!var) {
|
||||
unsetenv_lock(name.c_str());
|
||||
} else {
|
||||
const std::string value = wcs2zstring(var->as_string());
|
||||
@ -288,8 +288,8 @@ static void handle_fish_use_posix_spawn_change(const environment_t &vars) {
|
||||
/// Allow the user to override the limit on how much data the `read` command will process.
|
||||
/// This is primarily for testing but could be used by users in special situations.
|
||||
static void handle_read_limit_change(const environment_t &vars) {
|
||||
auto read_byte_limit_var = vars.get(L"fish_read_limit");
|
||||
if (!read_byte_limit_var.missing_or_empty()) {
|
||||
auto read_byte_limit_var = vars.get_unless_empty(L"fish_read_limit");
|
||||
if (read_byte_limit_var) {
|
||||
size_t limit = fish_wcstoull(read_byte_limit_var->as_string().c_str());
|
||||
if (errno) {
|
||||
FLOGF(warning, "Ignoring fish_read_limit since it is not valid");
|
||||
@ -302,7 +302,7 @@ static void handle_read_limit_change(const environment_t &vars) {
|
||||
}
|
||||
|
||||
static void handle_fish_trace(const environment_t &vars) {
|
||||
trace_set_enabled(!vars.get(L"fish_trace").missing_or_empty());
|
||||
trace_set_enabled(vars.get_unless_empty(L"fish_trace").has_value());
|
||||
}
|
||||
|
||||
/// Populate the dispatch table used by `env_dispatch_var_change()` to efficiently call the
|
||||
@ -454,8 +454,8 @@ static void initialize_curses_using_fallbacks(const environment_t &vars) {
|
||||
const wchar_t *const fallbacks[] = {L"xterm-256color", L"xterm", L"ansi", L"dumb"};
|
||||
|
||||
wcstring termstr = L"";
|
||||
auto term_var = vars.get(L"TERM");
|
||||
if (!term_var.missing_or_empty()) {
|
||||
auto term_var = vars.get_unless_empty(L"TERM");
|
||||
if (term_var) {
|
||||
termstr = term_var->as_string();
|
||||
}
|
||||
|
||||
@ -541,8 +541,8 @@ static void apply_term_hacks(const environment_t &vars) {
|
||||
static const wchar_t *const title_terms[] = {L"xterm", L"screen", L"tmux", L"nxterm",
|
||||
L"rxvt", L"alacritty", L"wezterm"};
|
||||
static bool does_term_support_setting_title(const environment_t &vars) {
|
||||
const auto term_var = vars.get(L"TERM");
|
||||
if (term_var.missing_or_empty()) return false;
|
||||
const auto term_var = vars.get_unless_empty(L"TERM");
|
||||
if (!term_var) return false;
|
||||
|
||||
const wcstring term_str = term_var->as_string();
|
||||
const wchar_t *term = term_str.c_str();
|
||||
@ -569,8 +569,8 @@ static bool does_term_support_setting_title(const environment_t &vars) {
|
||||
static void init_curses(const environment_t &vars) {
|
||||
for (const auto &var_name : curses_variables) {
|
||||
std::string name = wcs2zstring(var_name);
|
||||
const auto var = vars.get(var_name, ENV_EXPORT);
|
||||
if (var.missing_or_empty()) {
|
||||
const auto var = vars.get_unless_empty(var_name, ENV_EXPORT);
|
||||
if (!var) {
|
||||
FLOGF(term_support, L"curses var %s missing or empty", name.c_str());
|
||||
unsetenv_lock(name.c_str());
|
||||
} else {
|
||||
@ -583,9 +583,9 @@ static void init_curses(const environment_t &vars) {
|
||||
int err_ret{0};
|
||||
if (setupterm(nullptr, STDOUT_FILENO, &err_ret) == ERR) {
|
||||
if (is_interactive_session()) {
|
||||
auto term = vars.get(L"TERM");
|
||||
auto term = vars.get_unless_empty(L"TERM");
|
||||
FLOGF(warning, _(L"Could not set up terminal."));
|
||||
if (term.missing_or_empty()) {
|
||||
if (!term) {
|
||||
FLOGF(warning, _(L"TERM environment variable not set."));
|
||||
} else {
|
||||
FLOGF(warning, _(L"TERM environment variable set to '%ls'."),
|
||||
@ -619,9 +619,9 @@ static void init_locale(const environment_t &vars) {
|
||||
char *old_msg_locale = strdup(setlocale(LC_MESSAGES, nullptr));
|
||||
|
||||
for (const auto &var_name : locale_variables) {
|
||||
const auto var = vars.get(var_name, ENV_EXPORT);
|
||||
const auto var = vars.get_unless_empty(var_name, ENV_EXPORT);
|
||||
std::string name = wcs2zstring(var_name);
|
||||
if (var.missing_or_empty()) {
|
||||
if (!var) {
|
||||
FLOGF(env_locale, L"locale var %s missing or empty", name.c_str());
|
||||
unsetenv_lock(name.c_str());
|
||||
} else {
|
||||
@ -637,8 +637,8 @@ static void init_locale(const environment_t &vars) {
|
||||
// A "C" locale is broken for our purposes - any wchar functions will break on it.
|
||||
// So we try *really really really hard* to not have one.
|
||||
bool fix_locale = true;
|
||||
if (auto allow_c = vars.get(L"fish_allow_singlebyte_locale")) {
|
||||
fix_locale = allow_c.missing_or_empty() ? true : !bool_from_string(allow_c->as_string());
|
||||
if (auto allow_c = vars.get_unless_empty(L"fish_allow_singlebyte_locale")) {
|
||||
fix_locale = !bool_from_string(allow_c->as_string());
|
||||
}
|
||||
if (fix_locale && MB_CUR_MAX == 1) {
|
||||
FLOGF(env_locale, L"Have singlebyte locale, trying to fix");
|
||||
|
@ -1204,7 +1204,7 @@ static int exec_subshell_internal(const wcstring &cmd, parser_t &parser,
|
||||
}
|
||||
});
|
||||
|
||||
const bool split_output = !parser.vars().get(L"IFS").missing_or_empty();
|
||||
const bool split_output = parser.vars().get_unless_empty(L"IFS").has_value();
|
||||
|
||||
// IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may
|
||||
// be null.
|
||||
|
@ -813,8 +813,8 @@ static void expand_home_directory(wcstring &input, const environment_t &vars) {
|
||||
maybe_t<wcstring> home;
|
||||
if (username.empty()) {
|
||||
// Current users home directory.
|
||||
auto home_var = vars.get(L"HOME");
|
||||
if (home_var.missing_or_empty()) {
|
||||
auto home_var = vars.get_unless_empty(L"HOME");
|
||||
if (!home_var) {
|
||||
input.clear();
|
||||
return;
|
||||
}
|
||||
|
@ -5746,13 +5746,6 @@ void test_maybe() {
|
||||
maybe_t<wcstring> n = none();
|
||||
do_test(!bool(n));
|
||||
|
||||
maybe_t<std::string> m2("abc");
|
||||
do_test(!m2.missing_or_empty());
|
||||
m2 = "";
|
||||
do_test(m2.missing_or_empty());
|
||||
m2 = none();
|
||||
do_test(m2.missing_or_empty());
|
||||
|
||||
maybe_t<std::string> m0 = none();
|
||||
maybe_t<std::string> m3("hi");
|
||||
maybe_t<std::string> m4 = m3;
|
||||
|
@ -111,8 +111,8 @@ static void autoload_names(std::unordered_set<wcstring> &names, bool get_hidden)
|
||||
|
||||
// TODO: justify this.
|
||||
auto &vars = env_stack_t::principal();
|
||||
const auto path_var = vars.get(L"fish_function_path");
|
||||
if (path_var.missing_or_empty()) return;
|
||||
const auto path_var = vars.get_unless_empty(L"fish_function_path");
|
||||
if (!path_var) return;
|
||||
|
||||
const std::vector<wcstring> &path_list = path_var->as_list();
|
||||
|
||||
|
@ -308,9 +308,8 @@ static bool is_potential_cd_path(const wcstring &path, bool at_cursor,
|
||||
directories.push_back(working_directory);
|
||||
} else {
|
||||
// Get the CDPATH.
|
||||
auto cdpath = ctx.vars.get(L"CDPATH");
|
||||
std::vector<wcstring> pathsv =
|
||||
cdpath.missing_or_empty() ? std::vector<wcstring>{L"."} : cdpath->as_list();
|
||||
auto cdpath = ctx.vars.get_unless_empty(L"CDPATH");
|
||||
std::vector<wcstring> pathsv = !cdpath ? std::vector<wcstring>{L"."} : cdpath->as_list();
|
||||
// The current $PWD is always valid.
|
||||
pathsv.push_back(L".");
|
||||
|
||||
@ -344,9 +343,9 @@ rgb_color_t highlight_color_resolver_t::resolve_spec_uncached(const highlight_sp
|
||||
rgb_color_t result = rgb_color_t::normal();
|
||||
highlight_role_t role = is_background ? highlight.background : highlight.foreground;
|
||||
|
||||
auto var = vars.get(get_highlight_var_name(role));
|
||||
if (var.missing_or_empty()) var = vars.get(get_highlight_var_name(get_fallback(role)));
|
||||
if (var.missing_or_empty()) var = vars.get(get_highlight_var_name(highlight_role_t::normal));
|
||||
auto var = vars.get_unless_empty(get_highlight_var_name(role));
|
||||
if (!var) var = vars.get_unless_empty(get_highlight_var_name(get_fallback(role)));
|
||||
if (!var) var = vars.get(get_highlight_var_name(highlight_role_t::normal));
|
||||
if (var) result = parse_color(*var, is_background);
|
||||
|
||||
// Handle modifiers.
|
||||
|
@ -1583,5 +1583,5 @@ void start_private_mode(env_stack_t &vars) {
|
||||
}
|
||||
|
||||
bool in_private_mode(const environment_t &vars) {
|
||||
return !vars.get(L"fish_private_mode").missing_or_empty();
|
||||
return vars.get_unless_empty(L"fish_private_mode").has_value();
|
||||
}
|
||||
|
@ -123,8 +123,8 @@ static readb_result_t readb(int in_fd) {
|
||||
// Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being
|
||||
// set.
|
||||
void update_wait_on_escape_ms(const environment_t& vars) {
|
||||
auto escape_time_ms = vars.get(L"fish_escape_delay_ms");
|
||||
if (escape_time_ms.missing_or_empty()) {
|
||||
auto escape_time_ms = vars.get_unless_empty(L"fish_escape_delay_ms");
|
||||
if (!escape_time_ms) {
|
||||
wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
|
||||
return;
|
||||
}
|
||||
|
@ -242,13 +242,6 @@ class maybe_t : private maybe_detail::conditionally_copyable_t<T> {
|
||||
const T &operator*() const { return value(); }
|
||||
T &operator*() { return value(); }
|
||||
|
||||
// Helper to replace missing_or_empty() on env_var_t.
|
||||
// Uses SFINAE to only introduce this function if T has an empty() type.
|
||||
template <typename S = T>
|
||||
decltype(S().empty(), bool()) missing_or_empty() const {
|
||||
return !has_value() || value().empty();
|
||||
}
|
||||
|
||||
// Compare values for equality.
|
||||
bool operator==(const maybe_t &rhs) const {
|
||||
if (this->has_value() && rhs.has_value()) return this->value() == rhs.value();
|
||||
|
@ -353,13 +353,13 @@ static base_directory_t make_base_directory(const wcstring &xdg_var,
|
||||
// uvars are available.
|
||||
const auto &vars = env_stack_t::globals();
|
||||
base_directory_t result{};
|
||||
const auto xdg_dir = vars.get(xdg_var, ENV_GLOBAL | ENV_EXPORT);
|
||||
if (!xdg_dir.missing_or_empty()) {
|
||||
const auto xdg_dir = vars.get_unless_empty(xdg_var, ENV_GLOBAL | ENV_EXPORT);
|
||||
if (xdg_dir) {
|
||||
result.path = xdg_dir->as_string() + L"/fish";
|
||||
result.used_xdg = true;
|
||||
} else {
|
||||
const auto home = vars.get(L"HOME", ENV_GLOBAL | ENV_EXPORT);
|
||||
if (!home.missing_or_empty()) {
|
||||
const auto home = vars.get_unless_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT);
|
||||
if (home) {
|
||||
result.path = home->as_string() + non_xdg_homepath;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user