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
56#[derive(Debug)]
58pub struct FileDescriptorTable {
59 fds: HashMap<i32, FileDescriptor>,
61 saved_fds: HashMap<i32, RawFd>,
63}
64
65impl FileDescriptorTable {
66 pub fn new() -> Self {
68 Self {
69 fds: HashMap::new(),
70 saved_fds: HashMap::new(),
71 }
72 }
73
74 pub fn open_fd(
88 &mut self,
89 fd_num: i32,
90 path: &str,
91 read: bool,
92 write: bool,
93 append: bool,
94 truncate: bool,
95 ) -> Result<(), String> {
96 if !(0..=9).contains(&fd_num) {
98 return Err(format!("Invalid file descriptor number: {}", fd_num));
99 }
100
101 let file = OpenOptions::new()
103 .read(read)
104 .write(write)
105 .append(append)
106 .truncate(truncate)
107 .create(write || append)
108 .open(path)
109 .map_err(|e| format!("Cannot open {}: {}", path, e))?;
110
111 self.fds.insert(fd_num, FileDescriptor::File(file));
113 Ok(())
114 }
115
116 pub fn duplicate_fd(&mut self, source_fd: i32, target_fd: i32) -> Result<(), String> {
126 if !(0..=9).contains(&source_fd) {
128 return Err(format!("Invalid source file descriptor: {}", source_fd));
129 }
130 if !(0..=9).contains(&target_fd) {
131 return Err(format!("Invalid target file descriptor: {}", target_fd));
132 }
133
134 if source_fd == target_fd {
136 return Ok(());
137 }
138
139 let raw_fd = match self.get_raw_fd(source_fd) {
141 Some(fd) => fd,
142 None => {
143 return Err(format!(
144 "File descriptor {} is not open or is closed",
145 source_fd
146 ))
147 }
148 };
149
150 self.fds
152 .insert(target_fd, FileDescriptor::Duplicate(raw_fd));
153 Ok(())
154 }
155
156 pub fn close_fd(&mut self, fd_num: i32) -> Result<(), String> {
165 if !(0..=9).contains(&fd_num) {
167 return Err(format!("Invalid file descriptor number: {}", fd_num));
168 }
169
170 self.fds.insert(fd_num, FileDescriptor::Closed);
172 Ok(())
173 }
174
175 pub fn save_fd(&mut self, fd_num: i32) -> Result<(), String> {
184 if !(0..=9).contains(&fd_num) {
186 return Err(format!("Invalid file descriptor number: {}", fd_num));
187 }
188
189 let saved_fd = unsafe {
191 let raw_fd = fd_num as RawFd;
192 libc::dup(raw_fd)
193 };
194
195 if saved_fd < 0 {
196 return Err(format!("Failed to save file descriptor {}", fd_num));
197 }
198
199 self.saved_fds.insert(fd_num, saved_fd);
200 Ok(())
201 }
202
203 pub fn restore_fd(&mut self, fd_num: i32) -> Result<(), String> {
212 if !(0..=9).contains(&fd_num) {
214 return Err(format!("Invalid file descriptor number: {}", fd_num));
215 }
216
217 if let Some(saved_fd) = self.saved_fds.remove(&fd_num) {
219 unsafe {
221 let result = libc::dup2(saved_fd, fd_num as RawFd);
222 libc::close(saved_fd); if result < 0 {
225 return Err(format!("Failed to restore file descriptor {}", fd_num));
226 }
227 }
228
229 self.fds.remove(&fd_num);
231 }
232
233 Ok(())
234 }
235
236 pub fn save_all_fds(&mut self) -> Result<(), String> {
242 let fd_nums: Vec<i32> = self.fds.keys().copied().collect();
244 for fd_num in fd_nums {
245 self.save_fd(fd_num)?;
246 }
247 Ok(())
248 }
249
250 pub fn restore_all_fds(&mut self) -> Result<(), String> {
256 let fd_nums: Vec<i32> = self.saved_fds.keys().copied().collect();
258 for fd_num in fd_nums {
259 self.restore_fd(fd_num)?;
260 }
261 Ok(())
262 }
263
264 #[allow(dead_code)]
273 pub fn get_stdio(&self, fd_num: i32) -> Option<Stdio> {
274 match self.fds.get(&fd_num) {
275 Some(FileDescriptor::File(file)) => {
276 let raw_fd = file.as_raw_fd();
278 let dup_fd = unsafe { libc::dup(raw_fd) };
279 if dup_fd >= 0 {
280 let file = unsafe { File::from_raw_fd(dup_fd) };
281 Some(Stdio::from(file))
282 } else {
283 None
284 }
285 }
286 Some(FileDescriptor::Duplicate(raw_fd)) => {
287 let dup_fd = unsafe { libc::dup(*raw_fd) };
289 if dup_fd >= 0 {
290 let file = unsafe { File::from_raw_fd(dup_fd) };
291 Some(Stdio::from(file))
292 } else {
293 None
294 }
295 }
296 Some(FileDescriptor::Closed) | None => None,
297 }
298 }
299
300 pub fn get_raw_fd(&self, fd_num: i32) -> Option<RawFd> {
309 match self.fds.get(&fd_num) {
310 Some(FileDescriptor::File(file)) => Some(file.as_raw_fd()),
311 Some(FileDescriptor::Duplicate(raw_fd)) => Some(*raw_fd),
312 Some(FileDescriptor::Closed) => None,
313 None => {
314 if fd_num >= 0 && fd_num <= 2 {
316 Some(fd_num as RawFd)
317 } else {
318 None
319 }
320 }
321 }
322 }
323
324 pub fn is_open(&self, fd_num: i32) -> bool {
333 matches!(
334 self.fds.get(&fd_num),
335 Some(FileDescriptor::File(_)) | Some(FileDescriptor::Duplicate(_))
336 )
337 }
338
339 pub fn is_closed(&self, fd_num: i32) -> bool {
348 matches!(self.fds.get(&fd_num), Some(FileDescriptor::Closed))
349 }
350
351 pub fn clear(&mut self) {
353 self.fds.clear();
354 self.saved_fds.clear();
355 }
356}
357
358impl Default for FileDescriptorTable {
359 fn default() -> Self {
360 Self::new()
361 }
362}
363
364#[derive(Debug, Clone)]
365pub struct ColorScheme {
366 pub prompt: String,
368 pub error: String,
370 pub success: String,
372 pub builtin: String,
374 pub directory: String,
376}
377
378impl Default for ColorScheme {
379 fn default() -> Self {
380 Self {
381 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(), }
387 }
388}
389
390#[derive(Debug, Clone)]
391pub struct ShellState {
392 pub variables: HashMap<String, String>,
394 pub exported: HashSet<String>,
396 pub last_exit_code: i32,
398 pub shell_pid: u32,
400 pub script_name: String,
402 pub dir_stack: Vec<String>,
404 pub aliases: HashMap<String, String>,
406 pub colors_enabled: bool,
408 pub color_scheme: ColorScheme,
410 pub positional_params: Vec<String>,
412 pub functions: HashMap<String, Ast>,
414 pub local_vars: Vec<HashMap<String, String>>,
416 pub function_depth: usize,
418 pub max_recursion_depth: usize,
420 pub returning: bool,
422 pub return_value: Option<i32>,
424 pub capture_output: Option<Rc<RefCell<Vec<u8>>>>,
426 pub condensed_cwd: bool,
428 pub trap_handlers: Arc<Mutex<HashMap<String, String>>>,
430 pub exit_trap_executed: bool,
432 pub exit_requested: bool,
434 pub exit_code: i32,
436 #[allow(dead_code)]
439 pub pending_signals: bool,
440 pub pending_heredoc_content: Option<String>,
442 pub collecting_heredoc: Option<(String, String, String)>, pub fd_table: Rc<RefCell<FileDescriptorTable>>,
446 pub subshell_depth: usize,
448}
449
450impl ShellState {
451 pub fn new() -> Self {
452 let shell_pid = std::process::id();
453
454 let no_color = env::var("NO_COLOR").is_ok();
456
457 let rush_colors = env::var("RUSH_COLORS")
459 .map(|v| v.to_lowercase())
460 .unwrap_or_else(|_| "auto".to_string());
461
462 let colors_enabled = match rush_colors.as_str() {
463 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
464 "0" | "false" | "off" | "disable" => false,
465 "auto" => !no_color && std::io::stdout().is_terminal(),
466 _ => !no_color && std::io::stdout().is_terminal(),
467 };
468
469 let rush_condensed = env::var("RUSH_CONDENSED")
471 .map(|v| v.to_lowercase())
472 .unwrap_or_else(|_| "true".to_string());
473
474 let condensed_cwd = match rush_condensed.as_str() {
475 "1" | "true" | "on" | "enable" => true,
476 "0" | "false" | "off" | "disable" => false,
477 _ => true, };
479
480 Self {
481 variables: HashMap::new(),
482 exported: HashSet::new(),
483 last_exit_code: 0,
484 shell_pid,
485 script_name: "rush".to_string(),
486 dir_stack: Vec::new(),
487 aliases: HashMap::new(),
488 colors_enabled,
489 color_scheme: ColorScheme::default(),
490 positional_params: Vec::new(),
491 functions: HashMap::new(),
492 local_vars: Vec::new(),
493 function_depth: 0,
494 max_recursion_depth: 500, returning: false,
496 return_value: None,
497 capture_output: None,
498 condensed_cwd,
499 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
500 exit_trap_executed: false,
501 exit_requested: false,
502 exit_code: 0,
503 pending_signals: false,
504 pending_heredoc_content: None,
505 collecting_heredoc: None,
506 fd_table: Rc::new(RefCell::new(FileDescriptorTable::new())),
507 subshell_depth: 0,
508 }
509 }
510
511 pub fn get_var(&self, name: &str) -> Option<String> {
513 match name {
515 "?" => Some(self.last_exit_code.to_string()),
516 "$" => Some(self.shell_pid.to_string()),
517 "0" => Some(self.script_name.clone()),
518 "*" => {
519 if self.positional_params.is_empty() {
521 Some("".to_string())
522 } else {
523 Some(self.positional_params.join(" "))
524 }
525 }
526 "@" => {
527 if self.positional_params.is_empty() {
529 Some("".to_string())
530 } else {
531 Some(self.positional_params.join(" "))
532 }
533 }
534 "#" => Some(self.positional_params.len().to_string()),
535 _ => {
536 if let Ok(index) = name.parse::<usize>()
538 && index > 0
539 && index <= self.positional_params.len()
540 {
541 return Some(self.positional_params[index - 1].clone());
542 }
543
544 for scope in self.local_vars.iter().rev() {
547 if let Some(value) = scope.get(name) {
548 return Some(value.clone());
549 }
550 }
551
552 if let Some(value) = self.variables.get(name) {
554 Some(value.clone())
555 } else {
556 env::var(name).ok()
558 }
559 }
560 }
561 }
562
563 pub fn set_var(&mut self, name: &str, value: String) {
565 for scope in self.local_vars.iter_mut().rev() {
568 if scope.contains_key(name) {
569 scope.insert(name.to_string(), value);
570 return;
571 }
572 }
573
574 self.variables.insert(name.to_string(), value);
576 }
577
578 pub fn unset_var(&mut self, name: &str) {
580 self.variables.remove(name);
581 self.exported.remove(name);
582 }
583
584 pub fn export_var(&mut self, name: &str) {
586 if self.variables.contains_key(name) {
587 self.exported.insert(name.to_string());
588 }
589 }
590
591 pub fn set_exported_var(&mut self, name: &str, value: String) {
593 self.set_var(name, value);
594 self.export_var(name);
595 }
596
597 pub fn get_env_for_child(&self) -> HashMap<String, String> {
599 let mut child_env = HashMap::new();
600
601 for (key, value) in env::vars() {
603 child_env.insert(key, value);
604 }
605
606 for var_name in &self.exported {
608 if let Some(value) = self.variables.get(var_name) {
609 child_env.insert(var_name.clone(), value.clone());
610 }
611 }
612
613 child_env
614 }
615
616 pub fn set_last_exit_code(&mut self, code: i32) {
618 self.last_exit_code = code;
619 }
620
621 pub fn set_script_name(&mut self, name: &str) {
623 self.script_name = name.to_string();
624 }
625
626 pub fn get_condensed_cwd(&self) -> String {
628 match env::current_dir() {
629 Ok(path) => {
630 let path_str = path.to_string_lossy();
631 let components: Vec<&str> = path_str.split('/').collect();
632 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
633 return "/".to_string();
634 }
635 let mut result = String::new();
636 for (i, comp) in components.iter().enumerate() {
637 if comp.is_empty() {
638 continue; }
640 if i == components.len() - 1 {
641 result.push('/');
642 result.push_str(comp);
643 } else {
644 result.push('/');
645 if let Some(first) = comp.chars().next() {
646 result.push(first);
647 }
648 }
649 }
650 if result.is_empty() {
651 "/".to_string()
652 } else {
653 result
654 }
655 }
656 Err(_) => "/?".to_string(), }
658 }
659
660 pub fn get_full_cwd(&self) -> String {
662 match env::current_dir() {
663 Ok(path) => path.to_string_lossy().to_string(),
664 Err(_) => "/?".to_string(), }
666 }
667
668 pub fn get_user_hostname(&self) -> String {
670 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
671
672 if let Ok(hostname) = env::var("HOSTNAME")
674 && !hostname.trim().is_empty()
675 {
676 return format!("{}@{}", user, hostname);
677 }
678
679 let hostname = match std::process::Command::new("hostname").output() {
681 Ok(output) if output.status.success() => {
682 String::from_utf8_lossy(&output.stdout).trim().to_string()
683 }
684 _ => "hostname".to_string(), };
686
687 if hostname != "hostname" {
689 unsafe {
690 env::set_var("HOSTNAME", &hostname);
691 }
692 }
693
694 format!("{}@{}", user, hostname)
695 }
696
697 pub fn get_prompt(&self) -> String {
699 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
700 let prompt_char = if user == "root" { "#" } else { "$" };
701 let cwd = if self.condensed_cwd {
702 self.get_condensed_cwd()
703 } else {
704 self.get_full_cwd()
705 };
706 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
707 }
708
709 pub fn set_alias(&mut self, name: &str, value: String) {
711 self.aliases.insert(name.to_string(), value);
712 }
713
714 pub fn get_alias(&self, name: &str) -> Option<&String> {
716 self.aliases.get(name)
717 }
718
719 pub fn remove_alias(&mut self, name: &str) {
721 self.aliases.remove(name);
722 }
723
724 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
726 &self.aliases
727 }
728
729 pub fn set_positional_params(&mut self, params: Vec<String>) {
731 self.positional_params = params;
732 }
733
734 #[allow(dead_code)]
736 pub fn get_positional_params(&self) -> &[String] {
737 &self.positional_params
738 }
739
740 pub fn shift_positional_params(&mut self, count: usize) {
742 if count > 0 {
743 for _ in 0..count {
744 if !self.positional_params.is_empty() {
745 self.positional_params.remove(0);
746 }
747 }
748 }
749 }
750
751 #[allow(dead_code)]
753 pub fn push_positional_param(&mut self, param: String) {
754 self.positional_params.push(param);
755 }
756
757 pub fn define_function(&mut self, name: String, body: Ast) {
759 self.functions.insert(name, body);
760 }
761
762 pub fn get_function(&self, name: &str) -> Option<&Ast> {
764 self.functions.get(name)
765 }
766
767 #[allow(dead_code)]
769 pub fn remove_function(&mut self, name: &str) {
770 self.functions.remove(name);
771 }
772
773 #[allow(dead_code)]
775 pub fn get_function_names(&self) -> Vec<&String> {
776 self.functions.keys().collect()
777 }
778
779 pub fn push_local_scope(&mut self) {
781 self.local_vars.push(HashMap::new());
782 }
783
784 pub fn pop_local_scope(&mut self) {
786 if !self.local_vars.is_empty() {
787 self.local_vars.pop();
788 }
789 }
790
791 pub fn set_local_var(&mut self, name: &str, value: String) {
793 if let Some(current_scope) = self.local_vars.last_mut() {
794 current_scope.insert(name.to_string(), value);
795 } else {
796 self.set_var(name, value);
798 }
799 }
800
801 pub fn enter_function(&mut self) {
803 self.function_depth += 1;
804 if self.function_depth > self.local_vars.len() {
805 self.push_local_scope();
806 }
807 }
808
809 pub fn exit_function(&mut self) {
811 if self.function_depth > 0 {
812 self.function_depth -= 1;
813 if self.function_depth == self.local_vars.len() - 1 {
814 self.pop_local_scope();
815 }
816 }
817 }
818
819 pub fn set_return(&mut self, value: i32) {
821 self.returning = true;
822 self.return_value = Some(value);
823 }
824
825 pub fn clear_return(&mut self) {
827 self.returning = false;
828 self.return_value = None;
829 }
830
831 pub fn is_returning(&self) -> bool {
833 self.returning
834 }
835
836 pub fn get_return_value(&self) -> Option<i32> {
838 self.return_value
839 }
840
841 pub fn set_trap(&mut self, signal: &str, command: String) {
843 if let Ok(mut handlers) = self.trap_handlers.lock() {
844 handlers.insert(signal.to_uppercase(), command);
845 }
846 }
847
848 pub fn get_trap(&self, signal: &str) -> Option<String> {
850 if let Ok(handlers) = self.trap_handlers.lock() {
851 handlers.get(&signal.to_uppercase()).cloned()
852 } else {
853 None
854 }
855 }
856
857 pub fn remove_trap(&mut self, signal: &str) {
859 if let Ok(mut handlers) = self.trap_handlers.lock() {
860 handlers.remove(&signal.to_uppercase());
861 }
862 }
863
864 pub fn get_all_traps(&self) -> HashMap<String, String> {
866 if let Ok(handlers) = self.trap_handlers.lock() {
867 handlers.clone()
868 } else {
869 HashMap::new()
870 }
871 }
872
873 #[allow(dead_code)]
875 pub fn clear_traps(&mut self) {
876 if let Ok(mut handlers) = self.trap_handlers.lock() {
877 handlers.clear();
878 }
879 }
880}
881
882pub fn enqueue_signal(signal_name: &str, signal_number: i32) {
885 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
886 if queue.len() >= MAX_SIGNAL_QUEUE_SIZE {
888 queue.pop_front();
889 eprintln!("Warning: Signal queue overflow, dropping oldest signal");
890 }
891
892 queue.push_back(SignalEvent::new(signal_name.to_string(), signal_number));
893 }
894}
895
896pub fn process_pending_signals(shell_state: &mut ShellState) {
899 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
901 while let Some(signal_event) = queue.pop_front() {
903 if let Some(trap_cmd) = shell_state.get_trap(&signal_event.signal_name)
905 && !trap_cmd.is_empty()
906 {
907 if shell_state.colors_enabled {
909 eprintln!(
910 "{}Signal {} (signal {}) received at {:?}\x1b[0m",
911 shell_state.color_scheme.builtin,
912 signal_event.signal_name,
913 signal_event.signal_number,
914 signal_event.timestamp
915 );
916 } else {
917 eprintln!(
918 "Signal {} (signal {}) received at {:?}",
919 signal_event.signal_name,
920 signal_event.signal_number,
921 signal_event.timestamp
922 );
923 }
924
925 crate::executor::execute_trap_handler(&trap_cmd, shell_state);
928 }
929 }
930 }
931}
932
933impl Default for ShellState {
934 fn default() -> Self {
935 Self::new()
936 }
937}
938
939#[cfg(test)]
940mod tests {
941 use super::*;
942 use std::sync::Mutex;
943
944 static FILE_LOCK: Mutex<()> = Mutex::new(());
946
947 #[test]
948 fn test_shell_state_basic() {
949 let mut state = ShellState::new();
950 state.set_var("TEST_VAR", "test_value".to_string());
951 assert_eq!(state.get_var("TEST_VAR"), Some("test_value".to_string()));
952 }
953
954 #[test]
955 fn test_special_variables() {
956 let mut state = ShellState::new();
957 state.set_last_exit_code(42);
958 state.set_script_name("test_script");
959
960 assert_eq!(state.get_var("?"), Some("42".to_string()));
961 assert_eq!(state.get_var("$"), Some(state.shell_pid.to_string()));
962 assert_eq!(state.get_var("0"), Some("test_script".to_string()));
963 }
964
965 #[test]
966 fn test_export_variable() {
967 let mut state = ShellState::new();
968 state.set_var("EXPORT_VAR", "export_value".to_string());
969 state.export_var("EXPORT_VAR");
970
971 let child_env = state.get_env_for_child();
972 assert_eq!(
973 child_env.get("EXPORT_VAR"),
974 Some(&"export_value".to_string())
975 );
976 }
977
978 #[test]
979 fn test_unset_variable() {
980 let mut state = ShellState::new();
981 state.set_var("UNSET_VAR", "value".to_string());
982 state.export_var("UNSET_VAR");
983
984 assert!(state.variables.contains_key("UNSET_VAR"));
985 assert!(state.exported.contains("UNSET_VAR"));
986
987 state.unset_var("UNSET_VAR");
988
989 assert!(!state.variables.contains_key("UNSET_VAR"));
990 assert!(!state.exported.contains("UNSET_VAR"));
991 }
992
993 #[test]
994 fn test_get_user_hostname() {
995 let state = ShellState::new();
996 let user_hostname = state.get_user_hostname();
997 assert!(user_hostname.contains('@'));
999 }
1000
1001 #[test]
1002 fn test_get_prompt() {
1003 let state = ShellState::new();
1004 let prompt = state.get_prompt();
1005 assert!(prompt.ends_with(" $ "));
1007 assert!(prompt.contains('@'));
1008 }
1009
1010 #[test]
1011 fn test_positional_parameters() {
1012 let mut state = ShellState::new();
1013 state.set_positional_params(vec![
1014 "arg1".to_string(),
1015 "arg2".to_string(),
1016 "arg3".to_string(),
1017 ]);
1018
1019 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1020 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1021 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1022 assert_eq!(state.get_var("4"), None);
1023 assert_eq!(state.get_var("#"), Some("3".to_string()));
1024 assert_eq!(state.get_var("*"), Some("arg1 arg2 arg3".to_string()));
1025 assert_eq!(state.get_var("@"), Some("arg1 arg2 arg3".to_string()));
1026 }
1027
1028 #[test]
1029 fn test_positional_parameters_empty() {
1030 let mut state = ShellState::new();
1031 state.set_positional_params(vec![]);
1032
1033 assert_eq!(state.get_var("1"), None);
1034 assert_eq!(state.get_var("#"), Some("0".to_string()));
1035 assert_eq!(state.get_var("*"), Some("".to_string()));
1036 assert_eq!(state.get_var("@"), Some("".to_string()));
1037 }
1038
1039 #[test]
1040 fn test_shift_positional_params() {
1041 let mut state = ShellState::new();
1042 state.set_positional_params(vec![
1043 "arg1".to_string(),
1044 "arg2".to_string(),
1045 "arg3".to_string(),
1046 ]);
1047
1048 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1049 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1050 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1051
1052 state.shift_positional_params(1);
1053
1054 assert_eq!(state.get_var("1"), Some("arg2".to_string()));
1055 assert_eq!(state.get_var("2"), Some("arg3".to_string()));
1056 assert_eq!(state.get_var("3"), None);
1057 assert_eq!(state.get_var("#"), Some("2".to_string()));
1058
1059 state.shift_positional_params(2);
1060
1061 assert_eq!(state.get_var("1"), None);
1062 assert_eq!(state.get_var("#"), Some("0".to_string()));
1063 }
1064
1065 #[test]
1066 fn test_push_positional_param() {
1067 let mut state = ShellState::new();
1068 state.set_positional_params(vec!["arg1".to_string()]);
1069
1070 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1071 assert_eq!(state.get_var("#"), Some("1".to_string()));
1072
1073 state.push_positional_param("arg2".to_string());
1074
1075 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1076 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1077 assert_eq!(state.get_var("#"), Some("2".to_string()));
1078 }
1079
1080 #[test]
1081 fn test_local_variable_scoping() {
1082 let mut state = ShellState::new();
1083
1084 state.set_var("global_var", "global_value".to_string());
1086 assert_eq!(
1087 state.get_var("global_var"),
1088 Some("global_value".to_string())
1089 );
1090
1091 state.push_local_scope();
1093
1094 state.set_local_var("global_var", "local_value".to_string());
1096 assert_eq!(state.get_var("global_var"), Some("local_value".to_string()));
1097
1098 state.set_local_var("local_var", "local_only".to_string());
1100 assert_eq!(state.get_var("local_var"), Some("local_only".to_string()));
1101
1102 state.pop_local_scope();
1104
1105 assert_eq!(
1107 state.get_var("global_var"),
1108 Some("global_value".to_string())
1109 );
1110 assert_eq!(state.get_var("local_var"), None);
1111 }
1112
1113 #[test]
1114 fn test_nested_local_scopes() {
1115 let mut state = ShellState::new();
1116
1117 state.set_var("test_var", "global".to_string());
1119
1120 state.push_local_scope();
1122 state.set_local_var("test_var", "level1".to_string());
1123 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1124
1125 state.push_local_scope();
1127 state.set_local_var("test_var", "level2".to_string());
1128 assert_eq!(state.get_var("test_var"), Some("level2".to_string()));
1129
1130 state.pop_local_scope();
1132 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1133
1134 state.pop_local_scope();
1136 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1137 }
1138
1139 #[test]
1140 fn test_variable_set_in_local_scope() {
1141 let mut state = ShellState::new();
1142
1143 state.set_var("test_var", "global".to_string());
1145 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1146
1147 state.push_local_scope();
1149 state.set_local_var("test_var", "local".to_string());
1150 assert_eq!(state.get_var("test_var"), Some("local".to_string()));
1151
1152 state.pop_local_scope();
1154 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1155 }
1156
1157 #[test]
1158 fn test_condensed_cwd_environment_variable() {
1159 let state = ShellState::new();
1161 assert!(state.condensed_cwd);
1162
1163 unsafe {
1165 env::set_var("RUSH_CONDENSED", "true");
1166 }
1167 let state = ShellState::new();
1168 assert!(state.condensed_cwd);
1169
1170 unsafe {
1172 env::set_var("RUSH_CONDENSED", "false");
1173 }
1174 let state = ShellState::new();
1175 assert!(!state.condensed_cwd);
1176
1177 unsafe {
1179 env::remove_var("RUSH_CONDENSED");
1180 }
1181 }
1182
1183 #[test]
1184 fn test_get_full_cwd() {
1185 let state = ShellState::new();
1186 let full_cwd = state.get_full_cwd();
1187 assert!(!full_cwd.is_empty());
1188 assert!(full_cwd.contains('/') || full_cwd.contains('\\'));
1190 }
1191
1192 #[test]
1193 fn test_prompt_with_condensed_setting() {
1194 let mut state = ShellState::new();
1195
1196 assert!(state.condensed_cwd);
1198 let prompt_condensed = state.get_prompt();
1199 assert!(prompt_condensed.contains('@'));
1200
1201 state.condensed_cwd = false;
1203 let prompt_full = state.get_prompt();
1204 assert!(prompt_full.contains('@'));
1205
1206 assert!(prompt_condensed.ends_with("$ ") || prompt_condensed.ends_with("# "));
1208 assert!(prompt_full.ends_with("$ ") || prompt_full.ends_with("# "));
1209 }
1210
1211 #[test]
1214 fn test_fd_table_creation() {
1215 let fd_table = FileDescriptorTable::new();
1216 assert!(!fd_table.is_open(0));
1217 assert!(!fd_table.is_open(1));
1218 assert!(!fd_table.is_open(2));
1219 }
1220
1221 #[test]
1222 fn test_fd_table_open_file() {
1223 let mut fd_table = FileDescriptorTable::new();
1224
1225 let temp_file = "/tmp/rush_test_fd_open.txt";
1227 std::fs::write(temp_file, "test content").unwrap();
1228
1229 let result = fd_table.open_fd(3, temp_file, true, false, false, false);
1231 assert!(result.is_ok());
1232 assert!(fd_table.is_open(3));
1233
1234 let _ = std::fs::remove_file(temp_file);
1236 }
1237
1238 #[test]
1239 fn test_fd_table_open_file_for_writing() {
1240 let mut fd_table = FileDescriptorTable::new();
1241
1242 let temp_file = "/tmp/rush_test_fd_write.txt";
1244
1245 let result = fd_table.open_fd(4, temp_file, false, true, false, true);
1247 assert!(result.is_ok());
1248 assert!(fd_table.is_open(4));
1249
1250 let _ = std::fs::remove_file(temp_file);
1252 }
1253
1254 #[test]
1255 fn test_fd_table_invalid_fd_number() {
1256 let mut fd_table = FileDescriptorTable::new();
1257
1258 let result = fd_table.open_fd(-1, "/tmp/test.txt", true, false, false, false);
1260 assert!(result.is_err());
1261 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1262
1263 let result = fd_table.open_fd(10, "/tmp/test.txt", true, false, false, false);
1264 assert!(result.is_err());
1265 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1266 }
1267
1268 #[test]
1269 fn test_fd_table_duplicate_fd() {
1270 let mut fd_table = FileDescriptorTable::new();
1271
1272 let temp_file = "/tmp/rush_test_fd_dup.txt";
1274 std::fs::write(temp_file, "test content").unwrap();
1275
1276 fd_table
1278 .open_fd(3, temp_file, true, false, false, false)
1279 .unwrap();
1280 assert!(fd_table.is_open(3));
1281
1282 let result = fd_table.duplicate_fd(3, 4);
1284 assert!(result.is_ok());
1285 assert!(fd_table.is_open(4));
1286
1287 let _ = std::fs::remove_file(temp_file);
1289 }
1290
1291 #[test]
1292 fn test_fd_table_duplicate_to_self() {
1293 let mut fd_table = FileDescriptorTable::new();
1294
1295 let temp_file = "/tmp/rush_test_fd_dup_self.txt";
1297 std::fs::write(temp_file, "test content").unwrap();
1298
1299 fd_table
1301 .open_fd(3, temp_file, true, false, false, false)
1302 .unwrap();
1303
1304 let result = fd_table.duplicate_fd(3, 3);
1306 assert!(result.is_ok());
1307 assert!(fd_table.is_open(3));
1308
1309 let _ = std::fs::remove_file(temp_file);
1311 }
1312
1313 #[test]
1314 fn test_fd_table_duplicate_closed_fd() {
1315 let mut fd_table = FileDescriptorTable::new();
1316
1317 let result = fd_table.duplicate_fd(3, 4);
1319 assert!(result.is_err());
1320 assert!(result.unwrap_err().contains("not open"));
1321 }
1322
1323 #[test]
1324 fn test_fd_table_close_fd() {
1325 let mut fd_table = FileDescriptorTable::new();
1326
1327 let temp_file = "/tmp/rush_test_fd_close.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 assert!(fd_table.is_open(3));
1336
1337 let result = fd_table.close_fd(3);
1339 assert!(result.is_ok());
1340 assert!(fd_table.is_closed(3));
1341 assert!(!fd_table.is_open(3));
1342
1343 let _ = std::fs::remove_file(temp_file);
1345 }
1346
1347 #[test]
1348 fn test_fd_table_save_and_restore() {
1349 let mut fd_table = FileDescriptorTable::new();
1350
1351 let result = fd_table.save_fd(0);
1353 assert!(result.is_ok());
1354
1355 let result = fd_table.restore_fd(0);
1357 assert!(result.is_ok());
1358 }
1359
1360 #[test]
1361 fn test_fd_table_save_all_and_restore_all() {
1362 let _lock = FILE_LOCK.lock().unwrap();
1363
1364 let mut fd_table = FileDescriptorTable::new();
1365
1366 use std::time::{SystemTime, UNIX_EPOCH};
1368 let timestamp = SystemTime::now()
1369 .duration_since(UNIX_EPOCH)
1370 .unwrap()
1371 .as_nanos();
1372 let temp_file1 = format!("/tmp/rush_test_fd_save1_{}.txt", timestamp);
1373 let temp_file2 = format!("/tmp/rush_test_fd_save2_{}.txt", timestamp);
1374
1375 std::fs::write(&temp_file1, "test content 1").unwrap();
1376 std::fs::write(&temp_file2, "test content 2").unwrap();
1377
1378 fd_table
1380 .open_fd(3, &temp_file1, true, false, false, false)
1381 .unwrap();
1382 fd_table
1383 .open_fd(4, &temp_file2, true, false, false, false)
1384 .unwrap();
1385
1386 let result = fd_table.save_all_fds();
1388 assert!(result.is_ok());
1389
1390 let result = fd_table.restore_all_fds();
1392 assert!(result.is_ok());
1393
1394 let _ = std::fs::remove_file(&temp_file1);
1396 let _ = std::fs::remove_file(&temp_file2);
1397 }
1398
1399 #[test]
1400 fn test_fd_table_clear() {
1401 let mut fd_table = FileDescriptorTable::new();
1402
1403 let temp_file = "/tmp/rush_test_fd_clear.txt";
1405 std::fs::write(temp_file, "test content").unwrap();
1406
1407 fd_table
1409 .open_fd(3, temp_file, true, false, false, false)
1410 .unwrap();
1411 assert!(fd_table.is_open(3));
1412
1413 fd_table.clear();
1415 assert!(!fd_table.is_open(3));
1416
1417 let _ = std::fs::remove_file(temp_file);
1419 }
1420
1421 #[test]
1422 fn test_fd_table_get_stdio() {
1423 let mut fd_table = FileDescriptorTable::new();
1424
1425 let temp_file = "/tmp/rush_test_fd_stdio.txt";
1427 std::fs::write(temp_file, "test content").unwrap();
1428
1429 fd_table
1431 .open_fd(3, temp_file, true, false, false, false)
1432 .unwrap();
1433
1434 let stdio = fd_table.get_stdio(3);
1436 assert!(stdio.is_some());
1437
1438 let stdio = fd_table.get_stdio(5);
1440 assert!(stdio.is_none());
1441
1442 let _ = std::fs::remove_file(temp_file);
1444 }
1445
1446 #[test]
1447 fn test_fd_table_multiple_operations() {
1448 let mut fd_table = FileDescriptorTable::new();
1449
1450 let temp_file1 = "/tmp/rush_test_fd_multi1.txt";
1452 let temp_file2 = "/tmp/rush_test_fd_multi2.txt";
1453 std::fs::write(temp_file1, "test content 1").unwrap();
1454 std::fs::write(temp_file2, "test content 2").unwrap();
1455
1456 fd_table
1458 .open_fd(3, temp_file1, true, false, false, false)
1459 .unwrap();
1460 assert!(fd_table.is_open(3));
1461
1462 fd_table.duplicate_fd(3, 4).unwrap();
1464 assert!(fd_table.is_open(4));
1465
1466 fd_table
1468 .open_fd(5, temp_file2, true, false, false, false)
1469 .unwrap();
1470 assert!(fd_table.is_open(5));
1471
1472 fd_table.close_fd(4).unwrap();
1474 assert!(fd_table.is_closed(4));
1475 assert!(!fd_table.is_open(4));
1476
1477 assert!(fd_table.is_open(3));
1479 assert!(fd_table.is_open(5));
1480
1481 let _ = std::fs::remove_file(temp_file1);
1483 let _ = std::fs::remove_file(temp_file2);
1484 }
1485
1486 #[test]
1487 fn test_shell_state_has_fd_table() {
1488 let state = ShellState::new();
1489 let fd_table = state.fd_table.borrow();
1490 assert!(!fd_table.is_open(3));
1491 }
1492
1493 #[test]
1494 fn test_shell_state_fd_table_operations() {
1495 let state = ShellState::new();
1496
1497 let temp_file = "/tmp/rush_test_state_fd.txt";
1499 std::fs::write(temp_file, "test content").unwrap();
1500
1501 {
1503 let mut fd_table = state.fd_table.borrow_mut();
1504 fd_table
1505 .open_fd(3, temp_file, true, false, false, false)
1506 .unwrap();
1507 }
1508
1509 {
1511 let fd_table = state.fd_table.borrow();
1512 assert!(fd_table.is_open(3));
1513 }
1514
1515 let _ = std::fs::remove_file(temp_file);
1517 }
1518}