347 lines
9.8 KiB
Rust
347 lines
9.8 KiB
Rust
use std::{collections::{HashMap, VecDeque}, ops::{Deref, Range}, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration};
|
|
|
|
use nix::unistd::{gethostname, getppid, User};
|
|
|
|
use crate::{exec_input, jobs::JobTab, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*};
|
|
|
|
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
|
|
|
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
|
|
|
|
pub static META_TABLE: LazyLock<RwLock<MetaTab>> = LazyLock::new(|| RwLock::new(MetaTab::new()));
|
|
|
|
|
|
thread_local! {
|
|
pub static LOGIC_TABLE: LazyLock<RwLock<LogTab>> = LazyLock::new(|| RwLock::new(LogTab::new()));
|
|
}
|
|
|
|
/// A shell function
|
|
///
|
|
/// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers to
|
|
/// The Node must be stored with the ParsedSrc because the tokens of the node contain an Rc<String>
|
|
/// Which refers to the String held in ParsedSrc
|
|
///
|
|
/// Can be dereferenced to pull out the wrapped Node
|
|
#[derive(Clone,Debug)]
|
|
pub struct ShFunc(Node);
|
|
|
|
impl ShFunc {
|
|
pub fn new(mut src: ParsedSrc) -> Self {
|
|
let body = Self::extract_brc_grp_hack(src.extract_nodes());
|
|
Self(body)
|
|
}
|
|
fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node {
|
|
// FIXME: find a better way to do this
|
|
let conjunction = tree.pop().unwrap();
|
|
let NdRule::Conjunction { mut elements } = conjunction.class else {
|
|
unreachable!()
|
|
};
|
|
let conjunct_node = elements.pop().unwrap();
|
|
let ConjunctNode { cmd, operator: _ } = conjunct_node;
|
|
*cmd
|
|
}
|
|
}
|
|
|
|
impl Deref for ShFunc {
|
|
type Target = Node;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
/// The logic table for the shell
|
|
///
|
|
/// Contains aliases and functions
|
|
pub struct LogTab {
|
|
functions: HashMap<String,ShFunc>,
|
|
aliases: HashMap<String,String>
|
|
}
|
|
|
|
impl LogTab {
|
|
pub fn new() -> Self {
|
|
Self { functions: HashMap::new(), aliases: HashMap::new() }
|
|
}
|
|
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
|
|
self.functions.insert(name.into(), src);
|
|
}
|
|
pub fn get_func(&self, name: &str) -> Option<ShFunc> {
|
|
self.functions.get(name).cloned()
|
|
}
|
|
pub fn funcs(&self) -> &HashMap<String,ShFunc> {
|
|
&self.functions
|
|
}
|
|
pub fn aliases(&self) -> &HashMap<String,String> {
|
|
&self.aliases
|
|
}
|
|
pub fn insert_alias(&mut self, name: &str, body: &str) {
|
|
self.aliases.insert(name.into(), body.into());
|
|
}
|
|
pub fn get_alias(&self, name: &str) -> Option<String> {
|
|
self.aliases.get(name).cloned()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct VarTab {
|
|
vars: HashMap<String,String>,
|
|
params: HashMap<char,String>,
|
|
sh_argv: VecDeque<String>, // Using a VecDeque makes the implementation of `shift` straightforward
|
|
}
|
|
|
|
impl VarTab {
|
|
pub fn new() -> Self {
|
|
let vars = HashMap::new();
|
|
let params = Self::init_params();
|
|
Self::init_env();
|
|
let mut var_tab = Self { vars, params, sh_argv: VecDeque::new() };
|
|
var_tab.init_sh_argv();
|
|
var_tab
|
|
}
|
|
fn init_params() -> HashMap<char, String> {
|
|
let mut params = HashMap::new();
|
|
params.insert('?', "0".into()); // Last command exit status
|
|
params.insert('#', "0".into()); // Number of positional parameters
|
|
params.insert('0', std::env::current_exe().unwrap().to_str().unwrap().to_string()); // Name of the shell
|
|
params.insert('$', Pid::this().to_string()); // PID of the shell
|
|
params.insert('!', "".into()); // PID of the last background job (if any)
|
|
params
|
|
}
|
|
fn init_env() {
|
|
let pathbuf_to_string = |pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();
|
|
// First, inherit any env vars from the parent process
|
|
let term = {
|
|
if isatty(1).unwrap() {
|
|
if let Ok(term) = std::env::var("TERM") {
|
|
term
|
|
} else {
|
|
"linux".to_string()
|
|
}
|
|
} else {
|
|
"xterm-256color".to_string()
|
|
}
|
|
};
|
|
let home;
|
|
let username;
|
|
let uid;
|
|
if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() {
|
|
home = user.dir;
|
|
username = user.name;
|
|
uid = user.uid;
|
|
} else {
|
|
home = PathBuf::new();
|
|
username = "unknown".into();
|
|
uid = 0.into();
|
|
}
|
|
let home = pathbuf_to_string(Ok(home));
|
|
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
|
|
|
|
env::set_var("IFS", " \t\n");
|
|
env::set_var("HOSTNAME", hostname);
|
|
env::set_var("UID", uid.to_string());
|
|
env::set_var("PPID", getppid().to_string());
|
|
env::set_var("TMPDIR", "/tmp");
|
|
env::set_var("TERM", term);
|
|
env::set_var("LANG", "en_US.UTF-8");
|
|
env::set_var("USER", username.clone());
|
|
env::set_var("LOGNAME", username);
|
|
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
|
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
|
env::set_var("HOME", home.clone());
|
|
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
|
env::set_var("FERN_HIST",format!("{}/.fernhist",home));
|
|
env::set_var("FERN_RC",format!("{}/.fernrc",home));
|
|
}
|
|
pub fn init_sh_argv(&mut self) {
|
|
for arg in env::args() {
|
|
self.bpush_arg(arg);
|
|
}
|
|
}
|
|
pub fn sh_argv(&self) -> &VecDeque<String> {
|
|
&self.sh_argv
|
|
}
|
|
pub fn sh_argv_mut(&mut self) -> &mut VecDeque<String> {
|
|
&mut self.sh_argv
|
|
}
|
|
pub fn clear_args(&mut self) {
|
|
self.sh_argv.clear();
|
|
// Push the current exe again
|
|
// This makes sure that $0 is always the current shell, no matter what
|
|
// It also updates the arg parameters '@' and '#' as well
|
|
self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string());
|
|
}
|
|
fn update_arg_params(&mut self) {
|
|
self.set_param('@', &self.sh_argv.clone().to_vec()[1..].join(" "));
|
|
self.set_param('#', &(self.sh_argv.len() - 1).to_string());
|
|
}
|
|
/// Push an arg to the front of the arg deque
|
|
pub fn fpush_arg(&mut self, arg: String) {
|
|
self.sh_argv.push_front(arg);
|
|
self.update_arg_params();
|
|
}
|
|
/// Push an arg to the back of the arg deque
|
|
pub fn bpush_arg(&mut self, arg: String) {
|
|
self.sh_argv.push_back(arg);
|
|
self.update_arg_params();
|
|
}
|
|
/// Pop an arg from the front of the arg deque
|
|
pub fn fpop_arg(&mut self) -> Option<String> {
|
|
let arg = self.sh_argv.pop_front();
|
|
self.update_arg_params();
|
|
arg
|
|
}
|
|
/// Pop an arg from the back of the arg deque
|
|
pub fn bpop_arg(&mut self) -> Option<String> {
|
|
let arg = self.sh_argv.pop_back();
|
|
self.update_arg_params();
|
|
arg
|
|
}
|
|
pub fn vars(&self) -> &HashMap<String,String> {
|
|
&self.vars
|
|
}
|
|
pub fn vars_mut(&mut self) -> &mut HashMap<String,String> {
|
|
&mut self.vars
|
|
}
|
|
pub fn params(&self) -> &HashMap<char,String> {
|
|
&self.params
|
|
}
|
|
pub fn params_mut(&mut self) -> &mut HashMap<char,String> {
|
|
&mut self.params
|
|
}
|
|
pub fn get_var(&self, var: &str) -> String {
|
|
if var.chars().count() == 1 {
|
|
let param = self.get_param(get_char(var, 0).unwrap());
|
|
if !param.is_empty() {
|
|
return param
|
|
}
|
|
}
|
|
if let Some(var) = self.vars.get(var).map(|s| s.to_string()) {
|
|
var
|
|
} else {
|
|
std::env::var(var).unwrap_or_default()
|
|
}
|
|
}
|
|
pub fn new_var(&mut self, var: &str, val: &str) {
|
|
self.vars.insert(var.to_string(), val.to_string());
|
|
}
|
|
pub fn set_param(&mut self, param: char, val: &str) {
|
|
self.params.insert(param,val.to_string());
|
|
}
|
|
pub fn get_param(&self, param: char) -> String {
|
|
if param.is_ascii_digit() {
|
|
let argv_idx = param
|
|
.to_string()
|
|
.parse::<usize>()
|
|
.unwrap();
|
|
let arg = self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default();
|
|
arg
|
|
} else if param == '?' {
|
|
self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".into())
|
|
} else {
|
|
self.params.get(¶m).map(|s| s.to_string()).unwrap_or_default()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A table of metadata for the shell
|
|
pub struct MetaTab {
|
|
runtime_start: Option<Instant>
|
|
}
|
|
|
|
impl MetaTab {
|
|
pub fn new() -> Self {
|
|
Self { runtime_start: None }
|
|
}
|
|
pub fn start_timer(&mut self) {
|
|
self.runtime_start = Some(Instant::now());
|
|
}
|
|
pub fn stop_timer(&mut self) -> Option<Duration> {
|
|
self.runtime_start
|
|
.take() // runtime_start returns to None
|
|
.map(|start| start.elapsed()) // return the duration, if any
|
|
}
|
|
}
|
|
|
|
/// Read from the job table
|
|
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
|
let lock = JOB_TABLE.read().unwrap();
|
|
f(lock)
|
|
}
|
|
|
|
/// Write to the job table
|
|
pub fn write_jobs<T, F: FnOnce(&mut RwLockWriteGuard<JobTab>) -> T>(f: F) -> T {
|
|
let lock = &mut JOB_TABLE.write().unwrap();
|
|
f(lock)
|
|
}
|
|
|
|
/// Read from the variable table
|
|
pub fn read_vars<T, F: FnOnce(RwLockReadGuard<VarTab>) -> T>(f: F) -> T {
|
|
let lock = VAR_TABLE.read().unwrap();
|
|
f(lock)
|
|
}
|
|
|
|
/// Write to the variable table
|
|
pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
|
|
let lock = &mut VAR_TABLE.write().unwrap();
|
|
f(lock)
|
|
}
|
|
|
|
pub fn read_meta<T, F: FnOnce(RwLockReadGuard<MetaTab>) -> T>(f: F) -> T {
|
|
let lock = META_TABLE.read().unwrap();
|
|
f(lock)
|
|
}
|
|
|
|
/// Write to the variable table
|
|
pub fn write_meta<T, F: FnOnce(&mut RwLockWriteGuard<MetaTab>) -> T>(f: F) -> T {
|
|
let lock = &mut META_TABLE.write().unwrap();
|
|
f(lock)
|
|
}
|
|
|
|
/// Read from the logic table
|
|
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
|
|
LOGIC_TABLE.with(|log| {
|
|
let lock = log.read().unwrap();
|
|
f(lock)
|
|
})
|
|
}
|
|
|
|
/// Write to the logic table
|
|
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
|
|
LOGIC_TABLE.with(|log| {
|
|
let lock = &mut log.write().unwrap();
|
|
f(lock)
|
|
})
|
|
}
|
|
|
|
pub fn get_status() -> i32 {
|
|
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
|
}
|
|
pub fn set_status(code: i32) {
|
|
write_vars(|v| v.set_param('?', &code.to_string()))
|
|
}
|
|
|
|
pub fn source_rc() -> ShResult<()> {
|
|
let path = if let Ok(path) = env::var("FERN_RC") {
|
|
PathBuf::from(&path)
|
|
} else {
|
|
let home = env::var("HOME").unwrap();
|
|
PathBuf::from(format!("{home}/.fernrc"))
|
|
};
|
|
if !path.exists() {
|
|
return Err(
|
|
ShErr::simple(ShErrKind::InternalErr, ".fernrc not found")
|
|
)
|
|
}
|
|
source_file(path)
|
|
}
|
|
|
|
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
|
let mut file = OpenOptions::new()
|
|
.read(true)
|
|
.open(path)?;
|
|
|
|
let mut buf = String::new();
|
|
file.read_to_string(&mut buf)?;
|
|
exec_input(buf)?;
|
|
Ok(())
|
|
}
|