1use super::parser::Ast;
2use lazy_static::lazy_static;
3use std::cell::RefCell;
4use std::collections::{HashMap, HashSet, VecDeque};
5use std::env;
6use std::fs::{File, OpenOptions};
7use std::io::IsTerminal;
8use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
9use std::process::Stdio;
10use std::rc::Rc;
11use std::sync::{Arc, Mutex};
12use std::time::Instant;
13
14lazy_static! {
15 pub static ref SIGNAL_QUEUE: Arc<Mutex<VecDeque<SignalEvent>>> =
18 Arc::new(Mutex::new(VecDeque::new()));
19}
20
21const MAX_SIGNAL_QUEUE_SIZE: usize = 100;
23
24#[derive(Debug, Clone)]
26pub struct SignalEvent {
27 pub signal_name: String,
29 pub signal_number: i32,
31 pub timestamp: Instant,
33}
34
35impl SignalEvent {
36 pub fn new(signal_name: String, signal_number: i32) -> Self {
37 Self {
38 signal_name,
39 signal_number,
40 timestamp: Instant::now(),
41 }
42 }
43}
44
45#[derive(Debug)]
47pub enum FileDescriptor {
48 File(File),
50 Duplicate(RawFd),
52 Closed,
54}
55
56impl FileDescriptor {
57 pub fn try_clone(&self) -> Result<Self, String> {
58 match self {
59 FileDescriptor::File(f) => {
60 let new_file = f
61 .try_clone()
62 .map_err(|e| format!("Failed to clone file: {}", e))?;
63 Ok(FileDescriptor::File(new_file))
64 }
65 FileDescriptor::Duplicate(fd) => Ok(FileDescriptor::Duplicate(*fd)),
66 FileDescriptor::Closed => Ok(FileDescriptor::Closed),
67 }
68 }
69}
70
71#[derive(Debug)]
73pub struct FileDescriptorTable {
74 fds: HashMap<i32, FileDescriptor>,
76 saved_fds: HashMap<i32, RawFd>,
78}
79
80impl FileDescriptorTable {
81 pub fn new() -> Self {
83 Self {
84 fds: HashMap::new(),
85 saved_fds: HashMap::new(),
86 }
87 }
88
89 pub fn open_fd(
103 &mut self,
104 fd_num: i32,
105 path: &str,
106 read: bool,
107 write: bool,
108 append: bool,
109 truncate: bool,
110 ) -> Result<(), String> {
111 if !(0..=1024).contains(&fd_num) {
113 return Err(format!("Invalid file descriptor number: {}", fd_num));
114 }
115
116 let file = OpenOptions::new()
118 .read(read)
119 .write(write)
120 .append(append)
121 .truncate(truncate)
122 .create(write || append)
123 .open(path)
124 .map_err(|e| format!("Cannot open {}: {}", path, e))?;
125
126 self.fds.insert(fd_num, FileDescriptor::File(file));
128 Ok(())
129 }
130
131 pub fn duplicate_fd(&mut self, source_fd: i32, target_fd: i32) -> Result<(), String> {
141 if !(0..=1024).contains(&source_fd) {
143 return Err(format!("Invalid source file descriptor: {}", source_fd));
144 }
145 if !(0..=1024).contains(&target_fd) {
146 return Err(format!("Invalid target file descriptor: {}", target_fd));
147 }
148
149 if source_fd == target_fd {
151 return Ok(());
152 }
153
154 let raw_fd = match self.get_raw_fd(source_fd) {
156 Some(fd) => fd,
157 None => {
158 return Err(format!(
159 "File descriptor {} is not open or is closed",
160 source_fd
161 ));
162 }
163 };
164
165 self.fds
167 .insert(target_fd, FileDescriptor::Duplicate(raw_fd));
168 Ok(())
169 }
170
171 pub fn close_fd(&mut self, fd_num: i32) -> Result<(), String> {
180 if !(0..=1024).contains(&fd_num) {
182 return Err(format!("Invalid file descriptor number: {}", fd_num));
183 }
184
185 self.fds.insert(fd_num, FileDescriptor::Closed);
187 Ok(())
188 }
189
190 pub fn save_fd(&mut self, fd_num: i32) -> Result<(), String> {
199 if !(0..=1024).contains(&fd_num) {
201 return Err(format!("Invalid file descriptor number: {}", fd_num));
202 }
203
204 let saved_fd = unsafe {
206 let raw_fd = fd_num as RawFd;
207 libc::dup(raw_fd)
208 };
209
210 if saved_fd < 0 {
211 return Err(format!("Failed to save file descriptor {}", fd_num));
212 }
213
214 self.saved_fds.insert(fd_num, saved_fd);
215 Ok(())
216 }
217
218 pub fn restore_fd(&mut self, fd_num: i32) -> Result<(), String> {
227 if !(0..=1024).contains(&fd_num) {
229 return Err(format!("Invalid file descriptor number: {}", fd_num));
230 }
231
232 if let Some(saved_fd) = self.saved_fds.remove(&fd_num) {
234 unsafe {
236 let result = libc::dup2(saved_fd, fd_num as RawFd);
237 libc::close(saved_fd); if result < 0 {
240 return Err(format!("Failed to restore file descriptor {}", fd_num));
241 }
242 }
243
244 self.fds.remove(&fd_num);
246 }
247
248 Ok(())
249 }
250
251 pub fn deep_clone(&self) -> Result<Self, String> {
254 let mut new_fds = HashMap::new();
255 for (fd, descriptor) in &self.fds {
256 new_fds.insert(*fd, descriptor.try_clone()?);
257 }
258
259 Ok(Self {
260 fds: new_fds,
261 saved_fds: self.saved_fds.clone(),
262 })
263 }
264
265 pub fn save_all_fds(&mut self) -> Result<(), String> {
271 let fd_nums: Vec<i32> = self.fds.keys().copied().collect();
273 for fd_num in fd_nums {
274 self.save_fd(fd_num)?;
275 }
276 Ok(())
277 }
278
279 pub fn restore_all_fds(&mut self) -> Result<(), String> {
285 let fd_nums: Vec<i32> = self.saved_fds.keys().copied().collect();
287 for fd_num in fd_nums {
288 self.restore_fd(fd_num)?;
289 }
290 Ok(())
291 }
292
293 #[allow(dead_code)]
302 pub fn get_stdio(&self, fd_num: i32) -> Option<Stdio> {
303 match self.fds.get(&fd_num) {
304 Some(FileDescriptor::File(file)) => {
305 let raw_fd = file.as_raw_fd();
307 let dup_fd = unsafe { libc::dup(raw_fd) };
308 if dup_fd >= 0 {
309 let file = unsafe { File::from_raw_fd(dup_fd) };
310 Some(Stdio::from(file))
311 } else {
312 None
313 }
314 }
315 Some(FileDescriptor::Duplicate(raw_fd)) => {
316 let dup_fd = unsafe { libc::dup(*raw_fd) };
318 if dup_fd >= 0 {
319 let file = unsafe { File::from_raw_fd(dup_fd) };
320 Some(Stdio::from(file))
321 } else {
322 None
323 }
324 }
325 Some(FileDescriptor::Closed) | None => None,
326 }
327 }
328
329 pub fn get_raw_fd(&self, fd_num: i32) -> Option<RawFd> {
338 match self.fds.get(&fd_num) {
339 Some(FileDescriptor::File(file)) => Some(file.as_raw_fd()),
340 Some(FileDescriptor::Duplicate(raw_fd)) => Some(*raw_fd),
341 Some(FileDescriptor::Closed) => None,
342 None => {
343 if fd_num >= 0 && fd_num <= 2 {
345 Some(fd_num as RawFd)
346 } else {
347 None
348 }
349 }
350 }
351 }
352
353 pub fn is_open(&self, fd_num: i32) -> bool {
362 matches!(
363 self.fds.get(&fd_num),
364 Some(FileDescriptor::File(_)) | Some(FileDescriptor::Duplicate(_))
365 )
366 }
367
368 pub fn is_closed(&self, fd_num: i32) -> bool {
377 matches!(self.fds.get(&fd_num), Some(FileDescriptor::Closed))
378 }
379
380 pub fn clear(&mut self) {
382 self.fds.clear();
383 self.saved_fds.clear();
384 }
385}
386
387impl Default for FileDescriptorTable {
388 fn default() -> Self {
389 Self::new()
390 }
391}
392
393#[derive(Debug, Clone)]
394pub struct ColorScheme {
395 pub prompt: String,
397 pub error: String,
399 pub success: String,
401 pub builtin: String,
403 pub directory: String,
405}
406
407impl Default for ColorScheme {
408 fn default() -> Self {
409 Self {
410 prompt: "\x1b[32m".to_string(), error: "\x1b[31m".to_string(), success: "\x1b[32m".to_string(), builtin: "\x1b[36m".to_string(), directory: "\x1b[34m".to_string(), }
416 }
417}
418
419#[derive(Debug, Clone)]
420pub struct ShellState {
421 pub variables: HashMap<String, String>,
423 pub exported: HashSet<String>,
425 pub last_exit_code: i32,
427 pub shell_pid: u32,
429 pub script_name: String,
431 pub dir_stack: Vec<String>,
433 pub aliases: HashMap<String, String>,
435 pub colors_enabled: bool,
437 pub color_scheme: ColorScheme,
439 pub positional_params: Vec<String>,
441 pub functions: HashMap<String, Ast>,
443 pub local_vars: Vec<HashMap<String, String>>,
445 pub function_depth: usize,
447 pub max_recursion_depth: usize,
449 pub returning: bool,
451 pub return_value: Option<i32>,
453 pub capture_output: Option<Rc<RefCell<Vec<u8>>>>,
455 pub condensed_cwd: bool,
457 pub trap_handlers: Arc<Mutex<HashMap<String, String>>>,
459 pub exit_trap_executed: bool,
461 pub exit_requested: bool,
463 pub exit_code: i32,
465 #[allow(dead_code)]
468 pub pending_signals: bool,
469 pub pending_heredoc_content: Option<String>,
471 pub collecting_heredoc: Option<(String, String, String)>, pub fd_table: Rc<RefCell<FileDescriptorTable>>,
475 pub subshell_depth: usize,
477 pub stdin_override: Option<RawFd>,
479}
480
481impl ShellState {
482 pub fn new() -> Self {
483 let shell_pid = std::process::id();
484
485 let no_color = env::var("NO_COLOR").is_ok();
487
488 let rush_colors = env::var("RUSH_COLORS")
490 .map(|v| v.to_lowercase())
491 .unwrap_or_else(|_| "auto".to_string());
492
493 let colors_enabled = match rush_colors.as_str() {
494 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
495 "0" | "false" | "off" | "disable" => false,
496 "auto" => !no_color && std::io::stdout().is_terminal(),
497 _ => !no_color && std::io::stdout().is_terminal(),
498 };
499
500 let rush_condensed = env::var("RUSH_CONDENSED")
502 .map(|v| v.to_lowercase())
503 .unwrap_or_else(|_| "true".to_string());
504
505 let condensed_cwd = match rush_condensed.as_str() {
506 "1" | "true" | "on" | "enable" => true,
507 "0" | "false" | "off" | "disable" => false,
508 _ => true, };
510
511 Self {
512 variables: HashMap::new(),
513 exported: HashSet::new(),
514 last_exit_code: 0,
515 shell_pid,
516 script_name: "rush".to_string(),
517 dir_stack: Vec::new(),
518 aliases: HashMap::new(),
519 colors_enabled,
520 color_scheme: ColorScheme::default(),
521 positional_params: Vec::new(),
522 functions: HashMap::new(),
523 local_vars: Vec::new(),
524 function_depth: 0,
525 max_recursion_depth: 500, returning: false,
527 return_value: None,
528 capture_output: None,
529 condensed_cwd,
530 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
531 exit_trap_executed: false,
532 exit_requested: false,
533 exit_code: 0,
534 pending_signals: false,
535 pending_heredoc_content: None,
536 collecting_heredoc: None,
537 fd_table: Rc::new(RefCell::new(FileDescriptorTable::new())),
538 subshell_depth: 0,
539 stdin_override: None,
540 }
541 }
542
543 pub fn get_var(&self, name: &str) -> Option<String> {
545 match name {
547 "?" => Some(self.last_exit_code.to_string()),
548 "$" => Some(self.shell_pid.to_string()),
549 "0" => Some(self.script_name.clone()),
550 "*" => {
551 if self.positional_params.is_empty() {
553 Some("".to_string())
554 } else {
555 Some(self.positional_params.join(" "))
556 }
557 }
558 "@" => {
559 if self.positional_params.is_empty() {
561 Some("".to_string())
562 } else {
563 Some(self.positional_params.join(" "))
564 }
565 }
566 "#" => Some(self.positional_params.len().to_string()),
567 _ => {
568 if let Ok(index) = name.parse::<usize>()
570 && index > 0
571 && index <= self.positional_params.len()
572 {
573 return Some(self.positional_params[index - 1].clone());
574 }
575
576 for scope in self.local_vars.iter().rev() {
579 if let Some(value) = scope.get(name) {
580 return Some(value.clone());
581 }
582 }
583
584 if let Some(value) = self.variables.get(name) {
586 Some(value.clone())
587 } else {
588 env::var(name).ok()
590 }
591 }
592 }
593 }
594
595 pub fn set_var(&mut self, name: &str, value: String) {
597 for scope in self.local_vars.iter_mut().rev() {
600 if scope.contains_key(name) {
601 scope.insert(name.to_string(), value);
602 return;
603 }
604 }
605
606 self.variables.insert(name.to_string(), value);
608 }
609
610 pub fn unset_var(&mut self, name: &str) {
612 self.variables.remove(name);
613 self.exported.remove(name);
614 }
615
616 pub fn export_var(&mut self, name: &str) {
618 if self.variables.contains_key(name) {
619 self.exported.insert(name.to_string());
620 }
621 }
622
623 pub fn set_exported_var(&mut self, name: &str, value: String) {
625 self.set_var(name, value);
626 self.export_var(name);
627 }
628
629 pub fn get_env_for_child(&self) -> HashMap<String, String> {
631 let mut child_env = HashMap::new();
632
633 for (key, value) in env::vars() {
635 child_env.insert(key, value);
636 }
637
638 for var_name in &self.exported {
640 if let Some(value) = self.variables.get(var_name) {
641 child_env.insert(var_name.clone(), value.clone());
642 }
643 }
644
645 child_env
646 }
647
648 pub fn set_last_exit_code(&mut self, code: i32) {
650 self.last_exit_code = code;
651 }
652
653 pub fn set_script_name(&mut self, name: &str) {
655 self.script_name = name.to_string();
656 }
657
658 pub fn get_condensed_cwd(&self) -> String {
660 match env::current_dir() {
661 Ok(path) => {
662 let path_str = path.to_string_lossy();
663 let components: Vec<&str> = path_str.split('/').collect();
664 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
665 return "/".to_string();
666 }
667 let mut result = String::new();
668 for (i, comp) in components.iter().enumerate() {
669 if comp.is_empty() {
670 continue; }
672 if i == components.len() - 1 {
673 result.push('/');
674 result.push_str(comp);
675 } else {
676 result.push('/');
677 if let Some(first) = comp.chars().next() {
678 result.push(first);
679 }
680 }
681 }
682 if result.is_empty() {
683 "/".to_string()
684 } else {
685 result
686 }
687 }
688 Err(_) => "/?".to_string(), }
690 }
691
692 pub fn get_full_cwd(&self) -> String {
694 match env::current_dir() {
695 Ok(path) => path.to_string_lossy().to_string(),
696 Err(_) => "/?".to_string(), }
698 }
699
700 pub fn get_user_hostname(&self) -> String {
702 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
703
704 if let Ok(hostname) = env::var("HOSTNAME")
706 && !hostname.trim().is_empty()
707 {
708 return format!("{}@{}", user, hostname);
709 }
710
711 let hostname = match std::process::Command::new("hostname").output() {
713 Ok(output) if output.status.success() => {
714 String::from_utf8_lossy(&output.stdout).trim().to_string()
715 }
716 _ => "hostname".to_string(), };
718
719 if hostname != "hostname" {
721 unsafe {
722 env::set_var("HOSTNAME", &hostname);
723 }
724 }
725
726 format!("{}@{}", user, hostname)
727 }
728
729 pub fn get_prompt(&self) -> String {
731 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
732 let prompt_char = if user == "root" { "#" } else { "$" };
733 let cwd = if self.condensed_cwd {
734 self.get_condensed_cwd()
735 } else {
736 self.get_full_cwd()
737 };
738 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
739 }
740
741 pub fn set_alias(&mut self, name: &str, value: String) {
743 self.aliases.insert(name.to_string(), value);
744 }
745
746 pub fn get_alias(&self, name: &str) -> Option<&String> {
748 self.aliases.get(name)
749 }
750
751 pub fn remove_alias(&mut self, name: &str) {
753 self.aliases.remove(name);
754 }
755
756 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
758 &self.aliases
759 }
760
761 pub fn set_positional_params(&mut self, params: Vec<String>) {
763 self.positional_params = params;
764 }
765
766 #[allow(dead_code)]
768 pub fn get_positional_params(&self) -> &[String] {
769 &self.positional_params
770 }
771
772 pub fn shift_positional_params(&mut self, count: usize) {
774 if count > 0 {
775 for _ in 0..count {
776 if !self.positional_params.is_empty() {
777 self.positional_params.remove(0);
778 }
779 }
780 }
781 }
782
783 #[allow(dead_code)]
785 pub fn push_positional_param(&mut self, param: String) {
786 self.positional_params.push(param);
787 }
788
789 pub fn define_function(&mut self, name: String, body: Ast) {
791 self.functions.insert(name, body);
792 }
793
794 pub fn get_function(&self, name: &str) -> Option<&Ast> {
796 self.functions.get(name)
797 }
798
799 #[allow(dead_code)]
801 pub fn remove_function(&mut self, name: &str) {
802 self.functions.remove(name);
803 }
804
805 #[allow(dead_code)]
807 pub fn get_function_names(&self) -> Vec<&String> {
808 self.functions.keys().collect()
809 }
810
811 pub fn push_local_scope(&mut self) {
813 self.local_vars.push(HashMap::new());
814 }
815
816 pub fn pop_local_scope(&mut self) {
818 if !self.local_vars.is_empty() {
819 self.local_vars.pop();
820 }
821 }
822
823 pub fn set_local_var(&mut self, name: &str, value: String) {
825 if let Some(current_scope) = self.local_vars.last_mut() {
826 current_scope.insert(name.to_string(), value);
827 } else {
828 self.set_var(name, value);
830 }
831 }
832
833 pub fn enter_function(&mut self) {
835 self.function_depth += 1;
836 if self.function_depth > self.local_vars.len() {
837 self.push_local_scope();
838 }
839 }
840
841 pub fn exit_function(&mut self) {
843 if self.function_depth > 0 {
844 self.function_depth -= 1;
845 if self.function_depth == self.local_vars.len() - 1 {
846 self.pop_local_scope();
847 }
848 }
849 }
850
851 pub fn set_return(&mut self, value: i32) {
853 self.returning = true;
854 self.return_value = Some(value);
855 }
856
857 pub fn clear_return(&mut self) {
859 self.returning = false;
860 self.return_value = None;
861 }
862
863 pub fn is_returning(&self) -> bool {
865 self.returning
866 }
867
868 pub fn get_return_value(&self) -> Option<i32> {
870 self.return_value
871 }
872
873 pub fn set_trap(&mut self, signal: &str, command: String) {
875 if let Ok(mut handlers) = self.trap_handlers.lock() {
876 handlers.insert(signal.to_uppercase(), command);
877 }
878 }
879
880 pub fn get_trap(&self, signal: &str) -> Option<String> {
882 if let Ok(handlers) = self.trap_handlers.lock() {
883 handlers.get(&signal.to_uppercase()).cloned()
884 } else {
885 None
886 }
887 }
888
889 pub fn remove_trap(&mut self, signal: &str) {
891 if let Ok(mut handlers) = self.trap_handlers.lock() {
892 handlers.remove(&signal.to_uppercase());
893 }
894 }
895
896 pub fn get_all_traps(&self) -> HashMap<String, String> {
898 if let Ok(handlers) = self.trap_handlers.lock() {
899 handlers.clone()
900 } else {
901 HashMap::new()
902 }
903 }
904
905 #[allow(dead_code)]
907 pub fn clear_traps(&mut self) {
908 if let Ok(mut handlers) = self.trap_handlers.lock() {
909 handlers.clear();
910 }
911 }
912}
913
914pub fn enqueue_signal(signal_name: &str, signal_number: i32) {
917 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
918 if queue.len() >= MAX_SIGNAL_QUEUE_SIZE {
920 queue.pop_front();
921 eprintln!("Warning: Signal queue overflow, dropping oldest signal");
922 }
923
924 queue.push_back(SignalEvent::new(signal_name.to_string(), signal_number));
925 }
926}
927
928pub fn process_pending_signals(shell_state: &mut ShellState) {
931 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
933 while let Some(signal_event) = queue.pop_front() {
935 if let Some(trap_cmd) = shell_state.get_trap(&signal_event.signal_name)
937 && !trap_cmd.is_empty()
938 {
939 if shell_state.colors_enabled {
941 eprintln!(
942 "{}Signal {} (signal {}) received at {:?}\x1b[0m",
943 shell_state.color_scheme.builtin,
944 signal_event.signal_name,
945 signal_event.signal_number,
946 signal_event.timestamp
947 );
948 } else {
949 eprintln!(
950 "Signal {} (signal {}) received at {:?}",
951 signal_event.signal_name,
952 signal_event.signal_number,
953 signal_event.timestamp
954 );
955 }
956
957 crate::executor::execute_trap_handler(&trap_cmd, shell_state);
960 }
961 }
962 }
963}
964
965impl Default for ShellState {
966 fn default() -> Self {
967 Self::new()
968 }
969}
970
971#[cfg(test)]
972mod tests {
973 use super::*;
974 use std::sync::Mutex;
975
976 static FILE_LOCK: Mutex<()> = Mutex::new(());
978
979 #[test]
980 fn test_shell_state_basic() {
981 let mut state = ShellState::new();
982 state.set_var("TEST_VAR", "test_value".to_string());
983 assert_eq!(state.get_var("TEST_VAR"), Some("test_value".to_string()));
984 }
985
986 #[test]
987 fn test_special_variables() {
988 let mut state = ShellState::new();
989 state.set_last_exit_code(42);
990 state.set_script_name("test_script");
991
992 assert_eq!(state.get_var("?"), Some("42".to_string()));
993 assert_eq!(state.get_var("$"), Some(state.shell_pid.to_string()));
994 assert_eq!(state.get_var("0"), Some("test_script".to_string()));
995 }
996
997 #[test]
998 fn test_export_variable() {
999 let mut state = ShellState::new();
1000 state.set_var("EXPORT_VAR", "export_value".to_string());
1001 state.export_var("EXPORT_VAR");
1002
1003 let child_env = state.get_env_for_child();
1004 assert_eq!(
1005 child_env.get("EXPORT_VAR"),
1006 Some(&"export_value".to_string())
1007 );
1008 }
1009
1010 #[test]
1011 fn test_unset_variable() {
1012 let mut state = ShellState::new();
1013 state.set_var("UNSET_VAR", "value".to_string());
1014 state.export_var("UNSET_VAR");
1015
1016 assert!(state.variables.contains_key("UNSET_VAR"));
1017 assert!(state.exported.contains("UNSET_VAR"));
1018
1019 state.unset_var("UNSET_VAR");
1020
1021 assert!(!state.variables.contains_key("UNSET_VAR"));
1022 assert!(!state.exported.contains("UNSET_VAR"));
1023 }
1024
1025 #[test]
1026 fn test_get_user_hostname() {
1027 let state = ShellState::new();
1028 let user_hostname = state.get_user_hostname();
1029 assert!(user_hostname.contains('@'));
1031 }
1032
1033 #[test]
1034 fn test_get_prompt() {
1035 let state = ShellState::new();
1036 let prompt = state.get_prompt();
1037 assert!(prompt.ends_with(" $ "));
1039 assert!(prompt.contains('@'));
1040 }
1041
1042 #[test]
1043 fn test_positional_parameters() {
1044 let mut state = ShellState::new();
1045 state.set_positional_params(vec![
1046 "arg1".to_string(),
1047 "arg2".to_string(),
1048 "arg3".to_string(),
1049 ]);
1050
1051 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1052 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1053 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1054 assert_eq!(state.get_var("4"), None);
1055 assert_eq!(state.get_var("#"), Some("3".to_string()));
1056 assert_eq!(state.get_var("*"), Some("arg1 arg2 arg3".to_string()));
1057 assert_eq!(state.get_var("@"), Some("arg1 arg2 arg3".to_string()));
1058 }
1059
1060 #[test]
1061 fn test_positional_parameters_empty() {
1062 let mut state = ShellState::new();
1063 state.set_positional_params(vec![]);
1064
1065 assert_eq!(state.get_var("1"), None);
1066 assert_eq!(state.get_var("#"), Some("0".to_string()));
1067 assert_eq!(state.get_var("*"), Some("".to_string()));
1068 assert_eq!(state.get_var("@"), Some("".to_string()));
1069 }
1070
1071 #[test]
1072 fn test_shift_positional_params() {
1073 let mut state = ShellState::new();
1074 state.set_positional_params(vec![
1075 "arg1".to_string(),
1076 "arg2".to_string(),
1077 "arg3".to_string(),
1078 ]);
1079
1080 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1081 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1082 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1083
1084 state.shift_positional_params(1);
1085
1086 assert_eq!(state.get_var("1"), Some("arg2".to_string()));
1087 assert_eq!(state.get_var("2"), Some("arg3".to_string()));
1088 assert_eq!(state.get_var("3"), None);
1089 assert_eq!(state.get_var("#"), Some("2".to_string()));
1090
1091 state.shift_positional_params(2);
1092
1093 assert_eq!(state.get_var("1"), None);
1094 assert_eq!(state.get_var("#"), Some("0".to_string()));
1095 }
1096
1097 #[test]
1098 fn test_push_positional_param() {
1099 let mut state = ShellState::new();
1100 state.set_positional_params(vec!["arg1".to_string()]);
1101
1102 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1103 assert_eq!(state.get_var("#"), Some("1".to_string()));
1104
1105 state.push_positional_param("arg2".to_string());
1106
1107 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1108 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1109 assert_eq!(state.get_var("#"), Some("2".to_string()));
1110 }
1111
1112 #[test]
1113 fn test_local_variable_scoping() {
1114 let mut state = ShellState::new();
1115
1116 state.set_var("global_var", "global_value".to_string());
1118 assert_eq!(
1119 state.get_var("global_var"),
1120 Some("global_value".to_string())
1121 );
1122
1123 state.push_local_scope();
1125
1126 state.set_local_var("global_var", "local_value".to_string());
1128 assert_eq!(state.get_var("global_var"), Some("local_value".to_string()));
1129
1130 state.set_local_var("local_var", "local_only".to_string());
1132 assert_eq!(state.get_var("local_var"), Some("local_only".to_string()));
1133
1134 state.pop_local_scope();
1136
1137 assert_eq!(
1139 state.get_var("global_var"),
1140 Some("global_value".to_string())
1141 );
1142 assert_eq!(state.get_var("local_var"), None);
1143 }
1144
1145 #[test]
1146 fn test_nested_local_scopes() {
1147 let mut state = ShellState::new();
1148
1149 state.set_var("test_var", "global".to_string());
1151
1152 state.push_local_scope();
1154 state.set_local_var("test_var", "level1".to_string());
1155 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1156
1157 state.push_local_scope();
1159 state.set_local_var("test_var", "level2".to_string());
1160 assert_eq!(state.get_var("test_var"), Some("level2".to_string()));
1161
1162 state.pop_local_scope();
1164 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1165
1166 state.pop_local_scope();
1168 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1169 }
1170
1171 #[test]
1172 fn test_variable_set_in_local_scope() {
1173 let mut state = ShellState::new();
1174
1175 state.set_var("test_var", "global".to_string());
1177 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1178
1179 state.push_local_scope();
1181 state.set_local_var("test_var", "local".to_string());
1182 assert_eq!(state.get_var("test_var"), Some("local".to_string()));
1183
1184 state.pop_local_scope();
1186 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1187 }
1188
1189 #[test]
1190 fn test_condensed_cwd_environment_variable() {
1191 let state = ShellState::new();
1193 assert!(state.condensed_cwd);
1194
1195 unsafe {
1197 env::set_var("RUSH_CONDENSED", "true");
1198 }
1199 let state = ShellState::new();
1200 assert!(state.condensed_cwd);
1201
1202 unsafe {
1204 env::set_var("RUSH_CONDENSED", "false");
1205 }
1206 let state = ShellState::new();
1207 assert!(!state.condensed_cwd);
1208
1209 unsafe {
1211 env::remove_var("RUSH_CONDENSED");
1212 }
1213 }
1214
1215 #[test]
1216 fn test_get_full_cwd() {
1217 let state = ShellState::new();
1218 let full_cwd = state.get_full_cwd();
1219 assert!(!full_cwd.is_empty());
1220 assert!(full_cwd.contains('/') || full_cwd.contains('\\'));
1222 }
1223
1224 #[test]
1225 fn test_prompt_with_condensed_setting() {
1226 let mut state = ShellState::new();
1227
1228 assert!(state.condensed_cwd);
1230 let prompt_condensed = state.get_prompt();
1231 assert!(prompt_condensed.contains('@'));
1232
1233 state.condensed_cwd = false;
1235 let prompt_full = state.get_prompt();
1236 assert!(prompt_full.contains('@'));
1237
1238 assert!(prompt_condensed.ends_with("$ ") || prompt_condensed.ends_with("# "));
1240 assert!(prompt_full.ends_with("$ ") || prompt_full.ends_with("# "));
1241 }
1242
1243 #[test]
1246 fn test_fd_table_creation() {
1247 let fd_table = FileDescriptorTable::new();
1248 assert!(!fd_table.is_open(0));
1249 assert!(!fd_table.is_open(1));
1250 assert!(!fd_table.is_open(2));
1251 }
1252
1253 #[test]
1254 fn test_fd_table_open_file() {
1255 let mut fd_table = FileDescriptorTable::new();
1256
1257 let temp_file = "/tmp/rush_test_fd_open.txt";
1259 std::fs::write(temp_file, "test content").unwrap();
1260
1261 let result = fd_table.open_fd(3, temp_file, true, false, false, false);
1263 assert!(result.is_ok());
1264 assert!(fd_table.is_open(3));
1265
1266 let _ = std::fs::remove_file(temp_file);
1268 }
1269
1270 #[test]
1271 fn test_fd_table_open_file_for_writing() {
1272 let mut fd_table = FileDescriptorTable::new();
1273
1274 let temp_file = "/tmp/rush_test_fd_write.txt";
1276
1277 let result = fd_table.open_fd(4, temp_file, false, true, false, true);
1279 assert!(result.is_ok());
1280 assert!(fd_table.is_open(4));
1281
1282 let _ = std::fs::remove_file(temp_file);
1284 }
1285
1286 #[test]
1287 fn test_fd_table_invalid_fd_number() {
1288 let mut fd_table = FileDescriptorTable::new();
1289
1290 let result = fd_table.open_fd(-1, "/tmp/test.txt", true, false, false, false);
1292 assert!(result.is_err());
1293 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1294
1295 let result = fd_table.open_fd(1025, "/tmp/test.txt", true, false, false, false);
1296 assert!(result.is_err());
1297 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1298 }
1299
1300 #[test]
1301 fn test_fd_table_duplicate_fd() {
1302 let mut fd_table = FileDescriptorTable::new();
1303
1304 let temp_file = "/tmp/rush_test_fd_dup.txt";
1306 std::fs::write(temp_file, "test content").unwrap();
1307
1308 fd_table
1310 .open_fd(3, temp_file, true, false, false, false)
1311 .unwrap();
1312 assert!(fd_table.is_open(3));
1313
1314 let result = fd_table.duplicate_fd(3, 4);
1316 assert!(result.is_ok());
1317 assert!(fd_table.is_open(4));
1318
1319 let _ = std::fs::remove_file(temp_file);
1321 }
1322
1323 #[test]
1324 fn test_fd_table_duplicate_to_self() {
1325 let mut fd_table = FileDescriptorTable::new();
1326
1327 let temp_file = "/tmp/rush_test_fd_dup_self.txt";
1329 std::fs::write(temp_file, "test content").unwrap();
1330
1331 fd_table
1333 .open_fd(3, temp_file, true, false, false, false)
1334 .unwrap();
1335
1336 let result = fd_table.duplicate_fd(3, 3);
1338 assert!(result.is_ok());
1339 assert!(fd_table.is_open(3));
1340
1341 let _ = std::fs::remove_file(temp_file);
1343 }
1344
1345 #[test]
1346 fn test_fd_table_duplicate_closed_fd() {
1347 let mut fd_table = FileDescriptorTable::new();
1348
1349 let result = fd_table.duplicate_fd(3, 4);
1351 assert!(result.is_err());
1352 assert!(result.unwrap_err().contains("not open"));
1353 }
1354
1355 #[test]
1356 fn test_fd_table_close_fd() {
1357 let mut fd_table = FileDescriptorTable::new();
1358
1359 let temp_file = "/tmp/rush_test_fd_close.txt";
1361 std::fs::write(temp_file, "test content").unwrap();
1362
1363 fd_table
1365 .open_fd(3, temp_file, true, false, false, false)
1366 .unwrap();
1367 assert!(fd_table.is_open(3));
1368
1369 let result = fd_table.close_fd(3);
1371 assert!(result.is_ok());
1372 assert!(fd_table.is_closed(3));
1373 assert!(!fd_table.is_open(3));
1374
1375 let _ = std::fs::remove_file(temp_file);
1377 }
1378
1379 #[test]
1380 fn test_fd_table_save_and_restore() {
1381 let mut fd_table = FileDescriptorTable::new();
1382
1383 let result = fd_table.save_fd(0);
1385 assert!(result.is_ok());
1386
1387 let result = fd_table.restore_fd(0);
1389 assert!(result.is_ok());
1390 }
1391
1392 #[test]
1393 fn test_fd_table_save_all_and_restore_all() {
1394 let _lock = FILE_LOCK.lock().unwrap();
1395 let mut fd_table = FileDescriptorTable::new();
1396
1397 use std::time::{SystemTime, UNIX_EPOCH};
1399 let timestamp = SystemTime::now()
1400 .duration_since(UNIX_EPOCH)
1401 .unwrap()
1402 .as_nanos();
1403 let temp_file1 = format!("/tmp/rush_test_fd_save1_{}.txt", timestamp);
1404 let temp_file2 = format!("/tmp/rush_test_fd_save2_{}.txt", timestamp);
1405
1406 std::fs::write(&temp_file1, "test content 1").unwrap();
1407 std::fs::write(&temp_file2, "test content 2").unwrap();
1408
1409 let f1 = File::open(&temp_file1).unwrap();
1413 let f2 = File::open(&temp_file2).unwrap();
1414 unsafe {
1415 libc::dup2(f1.as_raw_fd(), 50);
1416 libc::dup2(f2.as_raw_fd(), 51);
1417 }
1418
1419 fd_table
1420 .open_fd(50, &temp_file1, true, false, false, false)
1421 .unwrap();
1422 fd_table
1423 .open_fd(51, &temp_file2, true, false, false, false)
1424 .unwrap();
1425
1426 let result = fd_table.save_all_fds();
1428 assert!(result.is_ok());
1429
1430 let result = fd_table.restore_all_fds();
1432 assert!(result.is_ok());
1433
1434 unsafe {
1436 libc::close(50);
1437 libc::close(51);
1438 }
1439 let _ = std::fs::remove_file(&temp_file1);
1440 let _ = std::fs::remove_file(&temp_file2);
1441 }
1442
1443 #[test]
1444 fn test_fd_table_clear() {
1445 let mut fd_table = FileDescriptorTable::new();
1446
1447 let temp_file = "/tmp/rush_test_fd_clear.txt";
1449 std::fs::write(temp_file, "test content").unwrap();
1450
1451 fd_table
1457 .open_fd(50, temp_file, true, false, false, false)
1458 .unwrap();
1459 assert!(fd_table.is_open(50));
1460
1461 fd_table.clear();
1463 assert!(!fd_table.is_open(3));
1464
1465 let _ = std::fs::remove_file(temp_file);
1467 }
1468
1469 #[test]
1470 fn test_fd_table_get_stdio() {
1471 let mut fd_table = FileDescriptorTable::new();
1472
1473 let temp_file = "/tmp/rush_test_fd_stdio.txt";
1475 std::fs::write(temp_file, "test content").unwrap();
1476
1477 fd_table
1479 .open_fd(3, temp_file, true, false, false, false)
1480 .unwrap();
1481
1482 let stdio = fd_table.get_stdio(3);
1484 assert!(stdio.is_some());
1485
1486 let stdio = fd_table.get_stdio(5);
1488 assert!(stdio.is_none());
1489
1490 let _ = std::fs::remove_file(temp_file);
1492 }
1493
1494 #[test]
1495 fn test_fd_table_multiple_operations() {
1496 let mut fd_table = FileDescriptorTable::new();
1497
1498 let temp_file1 = "/tmp/rush_test_fd_multi1.txt";
1500 let temp_file2 = "/tmp/rush_test_fd_multi2.txt";
1501 std::fs::write(temp_file1, "test content 1").unwrap();
1502 std::fs::write(temp_file2, "test content 2").unwrap();
1503
1504 fd_table
1506 .open_fd(3, temp_file1, true, false, false, false)
1507 .unwrap();
1508 assert!(fd_table.is_open(3));
1509
1510 fd_table.duplicate_fd(3, 4).unwrap();
1512 assert!(fd_table.is_open(4));
1513
1514 fd_table
1516 .open_fd(5, temp_file2, true, false, false, false)
1517 .unwrap();
1518 assert!(fd_table.is_open(5));
1519
1520 fd_table.close_fd(4).unwrap();
1522 assert!(fd_table.is_closed(4));
1523 assert!(!fd_table.is_open(4));
1524
1525 assert!(fd_table.is_open(3));
1527 assert!(fd_table.is_open(5));
1528
1529 let _ = std::fs::remove_file(temp_file1);
1531 let _ = std::fs::remove_file(temp_file2);
1532 }
1533
1534 #[test]
1535 fn test_shell_state_has_fd_table() {
1536 let state = ShellState::new();
1537 let fd_table = state.fd_table.borrow();
1538 assert!(!fd_table.is_open(3));
1539 }
1540
1541 #[test]
1542 fn test_shell_state_fd_table_operations() {
1543 let state = ShellState::new();
1544
1545 let temp_file = "/tmp/rush_test_state_fd.txt";
1547 std::fs::write(temp_file, "test content").unwrap();
1548
1549 {
1551 let mut fd_table = state.fd_table.borrow_mut();
1552 fd_table
1553 .open_fd(3, temp_file, true, false, false, false)
1554 .unwrap();
1555 }
1556
1557 {
1559 let fd_table = state.fd_table.borrow();
1560 assert!(fd_table.is_open(3));
1561 }
1562
1563 let _ = std::fs::remove_file(temp_file);
1565 }
1566}