Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
@@ -0,0 +1,724 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rand::RngExt as _;
|
||||
|
||||
use crate::shell::ShellState;
|
||||
use crate::{Shell, ShellValue, ShellVariable, error, extensions, sys, variables};
|
||||
|
||||
const BASH_MAJOR: u32 = 5;
|
||||
const BASH_MINOR: u32 = 2;
|
||||
const BASH_PATCH: u32 = 37;
|
||||
const BASH_BUILD: u32 = 1;
|
||||
const BASH_RELEASE: &str = "release";
|
||||
const BASH_MACHINE: &str = "unknown";
|
||||
|
||||
const DEFAULT_LINENO: usize = 1;
|
||||
|
||||
/// Inherit environment variables from the host process into the shell's environment.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `shell` - The shell instance to inherit environment variables into.
|
||||
pub(crate) fn inherit_env_vars(
|
||||
shell: &mut Shell<impl extensions::ShellExtensions>,
|
||||
) -> Result<(), error::Error> {
|
||||
for (k, v) in sys::env::get_host_env_vars() {
|
||||
// See if it's a function exported by an ancestor process.
|
||||
if let Some(func_name) = k.strip_prefix("BASH_FUNC_")
|
||||
&& let Some(func_name) = func_name.strip_suffix("%%")
|
||||
{
|
||||
// Intentionally best-effort; don't fail out of the shell if we can't
|
||||
// parse an incoming function.
|
||||
if shell.define_func_from_str(func_name, v.as_str()).is_ok()
|
||||
&& let Some(func) = shell.func_mut(func_name)
|
||||
{
|
||||
func.export();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Special case OLDPWD for bash compatibility.
|
||||
if k == "OLDPWD" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut var = ShellVariable::new(ShellValue::String(v));
|
||||
var.export();
|
||||
shell.env_mut().set_global(k, var)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub(crate) fn init_well_known_vars(
|
||||
shell: &mut Shell<impl extensions::ShellExtensions>,
|
||||
) -> Result<(), error::Error> {
|
||||
let shell_version = shell.version().map(ToString::to_string);
|
||||
shell.env_mut().set_global(
|
||||
"BRUSH_VERSION",
|
||||
ShellVariable::new(shell_version.unwrap_or_default()),
|
||||
)?;
|
||||
|
||||
// BASH
|
||||
if let Some(shell_name) = shell.current_shell_name().map(|s| s.to_string()) {
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("BASH", ShellVariable::new(shell_name.clone()))?;
|
||||
// Initialize $_ to the shell name ($0).
|
||||
shell.update_last_arg_variable(Some(shell_name));
|
||||
}
|
||||
|
||||
// BASHOPTS
|
||||
let mut bashopts_var = ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| shell.options().shopt_optstr().into(),
|
||||
setter: |_| (),
|
||||
});
|
||||
bashopts_var.set_readonly();
|
||||
shell.env_mut().set_global("BASHOPTS", bashopts_var)?;
|
||||
|
||||
// BASHPID
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
let mut bashpid_var =
|
||||
ShellVariable::new(ShellValue::String(std::process::id().to_string()));
|
||||
bashpid_var.treat_as_integer();
|
||||
shell.env_mut().set_global("BASHPID", bashpid_var)?;
|
||||
}
|
||||
|
||||
// BASH_ALIASES
|
||||
shell.env_mut().set_global(
|
||||
"BASH_ALIASES",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| {
|
||||
let values = variables::ArrayLiteral(
|
||||
shell
|
||||
.aliases()
|
||||
.iter()
|
||||
.map(|(k, v)| (Some(k.to_owned()), v.to_owned()))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
ShellValue::associative_array_from_literals(values)
|
||||
.unwrap_or_else(|_error| ShellValue::AssociativeArray(BTreeMap::new()))
|
||||
},
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// BASH_ARGC
|
||||
shell.env_mut().set_global(
|
||||
"BASH_ARGC",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| get_bash_argc_value(shell),
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// BASH_ARGV
|
||||
shell.env_mut().set_global(
|
||||
"BASH_ARGV",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| get_bash_argv_value(shell),
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// BASH_ARGV0
|
||||
shell.env_mut().set_global(
|
||||
"BASH_ARGV0",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| {
|
||||
let argv0 = shell.current_shell_name().unwrap_or_default();
|
||||
argv0.to_string().into()
|
||||
},
|
||||
// TODO(vars): implement updating BASH_ARGV0
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// TODO(vars): implement mutation of BASH_CMDS
|
||||
shell.env_mut().set_global(
|
||||
"BASH_CMDS",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| {
|
||||
shell
|
||||
.program_location_cache()
|
||||
.to_value()
|
||||
.unwrap_or_else(|_error| ShellValue::AssociativeArray(BTreeMap::new()))
|
||||
},
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// TODO(vars): implement BASH_COMMAND
|
||||
// TODO(vars): implement BASH_EXECUTION_STRING
|
||||
|
||||
// BASH_LINENO
|
||||
shell.env_mut().set_global(
|
||||
"BASH_LINENO",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| get_bash_lineno_value(shell),
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// BASH_SOURCE
|
||||
shell.env_mut().set_global(
|
||||
"BASH_SOURCE",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| get_bash_source_value(shell),
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// BASH_SUBSHELL
|
||||
shell.env_mut().set_global(
|
||||
"BASH_SUBSHELL",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| shell.depth().to_string().into(),
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// BASH_VERSINFO
|
||||
let mut bash_versinfo_var = ShellVariable::new(ShellValue::indexed_array_from_strs(
|
||||
[
|
||||
BASH_MAJOR.to_string().as_str(),
|
||||
BASH_MINOR.to_string().as_str(),
|
||||
BASH_PATCH.to_string().as_str(),
|
||||
BASH_BUILD.to_string().as_str(),
|
||||
BASH_RELEASE,
|
||||
BASH_MACHINE,
|
||||
]
|
||||
.as_slice(),
|
||||
));
|
||||
bash_versinfo_var.set_readonly();
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("BASH_VERSINFO", bash_versinfo_var)?;
|
||||
|
||||
// BASH_VERSION
|
||||
// This is the Bash interface version. See BRUSH_VERSION for its implementation version.
|
||||
shell.env_mut().set_global(
|
||||
"BASH_VERSION",
|
||||
ShellVariable::new(std::format!(
|
||||
"{BASH_MAJOR}.{BASH_MINOR}.{BASH_PATCH}({BASH_BUILD})-{BASH_RELEASE}"
|
||||
)),
|
||||
)?;
|
||||
|
||||
// COMP_WORDBREAKS
|
||||
let mut default_comp_wordbreaks = String::from(" \t\n\"\'><=;|&(:");
|
||||
if shell.options().enable_hostname_completion {
|
||||
default_comp_wordbreaks.push('@');
|
||||
}
|
||||
|
||||
shell.env_mut().set_global(
|
||||
"COMP_WORDBREAKS",
|
||||
ShellVariable::new(default_comp_wordbreaks),
|
||||
)?;
|
||||
|
||||
// DIRSTACK
|
||||
shell.env_mut().set_global(
|
||||
"DIRSTACK",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| {
|
||||
shell
|
||||
.directory_stack()
|
||||
.iter()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
},
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// EPOCHREALTIME
|
||||
shell.env_mut().set_global(
|
||||
"EPOCHREALTIME",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |_shell| {
|
||||
let now = std::time::SystemTime::now();
|
||||
let since_epoch = now
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
since_epoch.as_secs_f64().to_string().into()
|
||||
},
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// EPOCHSECONDS
|
||||
shell.env_mut().set_global(
|
||||
"EPOCHSECONDS",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |_shell| {
|
||||
let now = std::time::SystemTime::now();
|
||||
let since_epoch = now
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
since_epoch.as_secs().to_string().into()
|
||||
},
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// EUID
|
||||
if let Ok(euid) = sys::users::get_effective_uid() {
|
||||
let mut euid_var = ShellVariable::new(ShellValue::String(format!("{euid}")));
|
||||
euid_var.treat_as_integer().set_readonly();
|
||||
shell.env_mut().set_global("EUID", euid_var)?;
|
||||
}
|
||||
|
||||
// FUNCNAME
|
||||
shell.env_mut().set_global(
|
||||
"FUNCNAME",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| get_funcname_value(shell),
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// GROUPS
|
||||
// N.B. We could compute this up front, but we choose to make it dynamic so that we
|
||||
// don't have to make costly system calls if the user never accesses it.
|
||||
shell.env_mut().set_global(
|
||||
"GROUPS",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |_shell| {
|
||||
let groups = get_current_user_gids();
|
||||
ShellValue::indexed_array_from_strings(
|
||||
groups.into_iter().map(|gid| gid.to_string()),
|
||||
)
|
||||
},
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// HISTCMD
|
||||
let mut histcmd_var = ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| {
|
||||
shell
|
||||
.history()
|
||||
.map_or_else(|| "0".into(), |h| h.count().to_string().into())
|
||||
},
|
||||
setter: |_| (),
|
||||
});
|
||||
histcmd_var.treat_as_integer();
|
||||
shell.env_mut().set_global("HISTCMD", histcmd_var)?;
|
||||
|
||||
// HISTFILE (if not already set)
|
||||
if !shell.env().is_set("HISTFILE")
|
||||
&& let Some(home_dir) = shell.home_dir()
|
||||
{
|
||||
let histfile = home_dir.join(".brush_history");
|
||||
shell.env_mut().set_global(
|
||||
"HISTFILE",
|
||||
ShellVariable::new(ShellValue::String(histfile.to_string_lossy().to_string())),
|
||||
)?;
|
||||
}
|
||||
|
||||
// HOSTNAME
|
||||
shell.env_mut().set_global(
|
||||
"HOSTNAME",
|
||||
ShellVariable::new(
|
||||
sys::network::get_hostname()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
),
|
||||
)?;
|
||||
|
||||
// HOSTTYPE
|
||||
shell.env_mut().set_global(
|
||||
"HOSTTYPE",
|
||||
ShellVariable::new(std::env::consts::ARCH.to_string()),
|
||||
)?;
|
||||
|
||||
// IFS
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("IFS", ShellVariable::new(" \t\n"))?;
|
||||
|
||||
// LINENO
|
||||
shell.env_mut().set_global(
|
||||
"LINENO",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| get_lineno(shell).to_string().into(),
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// MACHTYPE
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("MACHTYPE", ShellVariable::new(BASH_MACHINE))?;
|
||||
|
||||
// OLDPWD (initialization)
|
||||
if !shell.env().is_set("OLDPWD") {
|
||||
let mut oldpwd_var =
|
||||
ShellVariable::new(ShellValue::Unset(variables::ShellValueUnsetType::Untyped));
|
||||
oldpwd_var.export();
|
||||
shell.env_mut().set_global("OLDPWD", oldpwd_var)?;
|
||||
}
|
||||
|
||||
// OPTERR
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("OPTERR", ShellVariable::new("1"))?;
|
||||
|
||||
// OPTIND
|
||||
let mut optind_var = ShellVariable::new("1");
|
||||
optind_var.treat_as_integer();
|
||||
shell.env_mut().set_global("OPTIND", optind_var)?;
|
||||
|
||||
// OSTYPE
|
||||
// Match bash's conventional OSTYPE on each platform so that shell scripts
|
||||
// branching on `[[ $OSTYPE == darwin* ]]` / `linux-gnu*` etc. (Homebrew
|
||||
// shellenv, nvm, asdf, ...) take the expected path. Real bash includes a
|
||||
// kernel-version suffix on macOS/BSDs (e.g. `darwin24`); we omit the
|
||||
// suffix for now since the common patterns all use prefix matching.
|
||||
let os_type = match std::env::consts::OS {
|
||||
"linux" => "linux-gnu",
|
||||
"android" => "linux-android",
|
||||
"macos" | "ios" | "tvos" | "watchos" | "visionos" => "darwin",
|
||||
"freebsd" => "freebsd",
|
||||
"netbsd" => "netbsd",
|
||||
"openbsd" => "openbsd",
|
||||
"dragonfly" => "dragonfly",
|
||||
"solaris" | "illumos" => "solaris",
|
||||
"windows" => "windows",
|
||||
_ => "unknown",
|
||||
};
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("OSTYPE", ShellVariable::new(os_type))?;
|
||||
|
||||
// PATH (if not already set)
|
||||
if !shell.env().is_set("PATH") {
|
||||
let default_path_str = std::env::join_paths(sys::fs::get_default_executable_search_paths())
|
||||
.unwrap_or_else(|_| PathBuf::from("").into());
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("PATH", ShellVariable::new(default_path_str))?;
|
||||
}
|
||||
|
||||
// PIPESTATUS
|
||||
// TODO(well-known-vars): Investigate what happens if this gets unset.
|
||||
// TODO(well-known-vars): Investigate if this needs to be saved/preserved across prompt display.
|
||||
shell.env_mut().set_global(
|
||||
"PIPESTATUS",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| {
|
||||
ShellValue::indexed_array_from_strings(
|
||||
shell.last_pipeline_statuses().iter().map(|s| s.to_string()),
|
||||
)
|
||||
},
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// PPID
|
||||
if let Some(ppid) = sys::terminal::get_parent_process_id() {
|
||||
let mut ppid_var = ShellVariable::new(ppid.to_string());
|
||||
ppid_var.treat_as_integer().set_readonly();
|
||||
shell.env_mut().set_global("PPID", ppid_var)?;
|
||||
}
|
||||
|
||||
// RANDOM
|
||||
let mut random_var = ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: get_random_value,
|
||||
setter: |_| (),
|
||||
});
|
||||
random_var.treat_as_integer();
|
||||
shell.env_mut().set_global("RANDOM", random_var)?;
|
||||
|
||||
// SECONDS
|
||||
shell.env_mut().set_global(
|
||||
"SECONDS",
|
||||
ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| {
|
||||
let now = std::time::SystemTime::now();
|
||||
let since_last = now
|
||||
.duration_since(shell.last_stopwatch_time())
|
||||
.unwrap_or_default();
|
||||
let total_seconds = since_last.as_secs() + u64::from(shell.last_stopwatch_offset());
|
||||
total_seconds.to_string().into()
|
||||
},
|
||||
// TODO(vars): implement updating SECONDS
|
||||
setter: |_| (),
|
||||
}),
|
||||
)?;
|
||||
|
||||
// SHELL (if not already set)
|
||||
if !shell.env().is_set("SHELL") {
|
||||
// Per docs, this should be the user's default login shell -- not the current shell.
|
||||
if let Some(default_shell) = sys::users::get_current_user_default_shell() {
|
||||
shell.env_mut().set_global(
|
||||
"SHELL",
|
||||
ShellVariable::new(default_shell.to_string_lossy().to_string()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// SHELLOPTS
|
||||
let mut shellopts_var = ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: |shell| shell.options().seto_optstr().into(),
|
||||
setter: |_| (),
|
||||
});
|
||||
shellopts_var.set_readonly();
|
||||
shell.env_mut().set_global("SHELLOPTS", shellopts_var)?;
|
||||
|
||||
// SHLVL
|
||||
let input_shlvl = shell.env_str("SHLVL").unwrap_or_else(|| "0".into());
|
||||
let updated_shlvl = input_shlvl.as_ref().parse::<u32>().unwrap_or(0) + 1;
|
||||
let mut shlvl_var = ShellVariable::new(updated_shlvl.to_string());
|
||||
shlvl_var.export();
|
||||
shell.env_mut().set_global("SHLVL", shlvl_var)?;
|
||||
|
||||
// SRANDOM
|
||||
let mut random_var = ShellVariable::new(ShellValue::Dynamic {
|
||||
getter: get_srandom_value,
|
||||
setter: |_| (),
|
||||
});
|
||||
random_var.treat_as_integer();
|
||||
shell.env_mut().set_global("SRANDOM", random_var)?;
|
||||
|
||||
// PS1 / PS2
|
||||
if shell.options().interactive {
|
||||
if !shell.env().is_set("PS1") {
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("PS1", ShellVariable::new(r"\s-\v\$ "))?;
|
||||
}
|
||||
|
||||
if !shell.env().is_set("PS2") {
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("PS2", ShellVariable::new("> "))?;
|
||||
}
|
||||
}
|
||||
|
||||
// PS4
|
||||
if !shell.env().is_set("PS4") {
|
||||
shell
|
||||
.env_mut()
|
||||
.set_global("PS4", ShellVariable::new("+ "))?;
|
||||
}
|
||||
|
||||
//
|
||||
// PWD
|
||||
//
|
||||
// Reflect our actual working directory. There's a chance
|
||||
// we inherited an out-of-sync version of the variable. Future updates
|
||||
// will be handled by set_working_dir().
|
||||
//
|
||||
let pwd = shell.working_dir().to_string_lossy().to_string();
|
||||
let mut pwd_var = ShellVariable::new(pwd);
|
||||
pwd_var.export();
|
||||
shell.env_mut().set_global("PWD", pwd_var)?;
|
||||
|
||||
// UID
|
||||
if let Ok(uid) = sys::users::get_current_uid() {
|
||||
let mut uid_var = ShellVariable::new(ShellValue::String(format!("{uid}")));
|
||||
uid_var.treat_as_integer().set_readonly();
|
||||
shell.env_mut().set_global("UID", uid_var)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a list of the current user's group IDs, with the effective GID at the front.
|
||||
fn get_current_user_gids() -> Vec<u32> {
|
||||
let mut groups = sys::users::get_user_group_ids().unwrap_or_default();
|
||||
|
||||
// If the effective GID is present but not in the first position in the list, then move
|
||||
// it there.
|
||||
if let Ok(gid) = sys::users::get_effective_gid() {
|
||||
if let Some(index) = groups.iter().position(|&g| g == gid) {
|
||||
if index > 0 {
|
||||
// Move it to the front.
|
||||
groups.remove(index);
|
||||
groups.insert(0, gid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groups
|
||||
}
|
||||
|
||||
fn get_random_value(_shell: &dyn ShellState) -> ShellValue {
|
||||
let mut rng = rand::rng();
|
||||
let num = rng.random_range(0..32768);
|
||||
let str = num.to_string();
|
||||
str.into()
|
||||
}
|
||||
|
||||
fn get_srandom_value(_shell: &dyn ShellState) -> ShellValue {
|
||||
let mut rng = rand::rng();
|
||||
let num: u32 = rng.random();
|
||||
let str = num.to_string();
|
||||
str.into()
|
||||
}
|
||||
|
||||
fn get_funcname_value(shell: &dyn ShellState) -> variables::ShellValue {
|
||||
let stack = shell.call_stack();
|
||||
|
||||
if stack.iter_function_calls().next().is_none() {
|
||||
ShellValue::Unset(variables::ShellValueUnsetType::IndexedArray)
|
||||
} else {
|
||||
// When in a function, include both functions and sourced scripts in the stack
|
||||
stack
|
||||
.iter()
|
||||
.filter_map(|frame| match &frame.frame_type {
|
||||
crate::callstack::FrameType::Function(func) => Some(func.function_name.as_str()),
|
||||
crate::callstack::FrameType::Script(script) => {
|
||||
// Only include sourced scripts, not run scripts
|
||||
if matches!(script.call_type, crate::callstack::ScriptCallType::Source) {
|
||||
Some("source")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
crate::callstack::FrameType::TrapHandler(_)
|
||||
| crate::callstack::FrameType::Eval
|
||||
| crate::callstack::FrameType::CommandString
|
||||
| crate::callstack::FrameType::InteractiveSession => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bash_lineno_value(shell: &dyn ShellState) -> variables::ShellValue {
|
||||
let stack = shell.call_stack();
|
||||
|
||||
// BASH_LINENO[$i] contains the line number where FUNCNAME[$i] was called
|
||||
// This is extracted from the call_site of each frame
|
||||
if stack.iter_function_calls().next().is_none() {
|
||||
ShellValue::Unset(variables::ShellValueUnsetType::IndexedArray)
|
||||
} else {
|
||||
stack
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(frame_idx, frame)| match &frame.frame_type {
|
||||
crate::callstack::FrameType::Function(..)
|
||||
| crate::callstack::FrameType::Script(..) => {
|
||||
let caller_idx = frame_idx + 1;
|
||||
if caller_idx < stack.depth() {
|
||||
let caller_frame = &stack[caller_idx];
|
||||
Some(
|
||||
caller_frame
|
||||
.current_line()
|
||||
.unwrap_or(DEFAULT_LINENO)
|
||||
.to_string(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
crate::callstack::FrameType::TrapHandler(_)
|
||||
| crate::callstack::FrameType::Eval
|
||||
| crate::callstack::FrameType::CommandString
|
||||
| crate::callstack::FrameType::InteractiveSession => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bash_source_value(shell: &dyn ShellState) -> variables::ShellValue {
|
||||
let stack = shell.call_stack();
|
||||
|
||||
if stack.iter_function_calls().next().is_none() {
|
||||
let top_frame = stack.iter_script_calls().next();
|
||||
top_frame
|
||||
.map_or_else(Vec::new, |frame| vec![frame.source_info.source.clone()])
|
||||
.into()
|
||||
} else {
|
||||
// When in a function, include both functions and sourced scripts in the stack
|
||||
// This mirrors the FUNCNAME array structure
|
||||
stack
|
||||
.iter()
|
||||
.filter_map(|frame| match &frame.frame_type {
|
||||
crate::callstack::FrameType::Function(func) => {
|
||||
Some(func.function.source().source.clone())
|
||||
}
|
||||
crate::callstack::FrameType::Script(script) => {
|
||||
// Only include sourced scripts (matching the "source" in FUNCNAME)
|
||||
if matches!(script.call_type, crate::callstack::ScriptCallType::Source) {
|
||||
Some(script.source_info.source.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
crate::callstack::FrameType::TrapHandler(_) | crate::callstack::FrameType::Eval => {
|
||||
None
|
||||
}
|
||||
crate::callstack::FrameType::CommandString
|
||||
| crate::callstack::FrameType::InteractiveSession => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bash_argc_value(shell: &dyn ShellState) -> variables::ShellValue {
|
||||
if !shell.options().enable_debugger {
|
||||
return ShellValue::indexed_array_from_strs(&[]);
|
||||
}
|
||||
|
||||
let stack = shell.call_stack();
|
||||
stack
|
||||
.iter()
|
||||
.filter_map(|frame| match &frame.frame_type {
|
||||
crate::callstack::FrameType::Function(..)
|
||||
| crate::callstack::FrameType::Script(..)
|
||||
| crate::callstack::FrameType::CommandString
|
||||
| crate::callstack::FrameType::InteractiveSession => Some(frame.args.len().to_string()),
|
||||
crate::callstack::FrameType::TrapHandler(_) | crate::callstack::FrameType::Eval => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn get_bash_argv_value(shell: &dyn ShellState) -> variables::ShellValue {
|
||||
if !shell.options().enable_debugger {
|
||||
return ShellValue::indexed_array_from_strs(&[]);
|
||||
}
|
||||
|
||||
let stack = shell.call_stack();
|
||||
let mut argv = Vec::new();
|
||||
|
||||
for frame in stack.iter() {
|
||||
let include = match &frame.frame_type {
|
||||
crate::callstack::FrameType::Function(..)
|
||||
| crate::callstack::FrameType::Script(..)
|
||||
| crate::callstack::FrameType::CommandString
|
||||
| crate::callstack::FrameType::InteractiveSession => true,
|
||||
crate::callstack::FrameType::TrapHandler(_) | crate::callstack::FrameType::Eval => {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if include {
|
||||
// Push args in reverse order per frame (last arg at lowest index = top of stack)
|
||||
for arg in frame.args.iter().rev() {
|
||||
argv.push(arg.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
argv.into()
|
||||
}
|
||||
|
||||
fn get_lineno(shell: &dyn ShellState) -> usize {
|
||||
shell
|
||||
.call_stack()
|
||||
.current_frame()
|
||||
.and_then(|frame| frame.current_line())
|
||||
.unwrap_or(DEFAULT_LINENO)
|
||||
}
|
||||
Reference in New Issue
Block a user