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
277 for fd in 0..=2 {
280 if !self.fds.contains_key(&fd) {
281 let _ = self.save_fd(fd);
283 }
284 }
285 Ok(())
286 }
287
288 pub fn restore_all_fds(&mut self) -> Result<(), String> {
294 let fd_nums: Vec<i32> = self.saved_fds.keys().copied().collect();
296 for fd_num in fd_nums {
297 self.restore_fd(fd_num)?;
298 }
299 Ok(())
300 }
301
302 #[allow(dead_code)]
311 pub fn get_stdio(&self, fd_num: i32) -> Option<Stdio> {
312 match self.fds.get(&fd_num) {
313 Some(FileDescriptor::File(file)) => {
314 let raw_fd = file.as_raw_fd();
316 let dup_fd = unsafe { libc::dup(raw_fd) };
317 if dup_fd >= 0 {
318 let file = unsafe { File::from_raw_fd(dup_fd) };
319 Some(Stdio::from(file))
320 } else {
321 None
322 }
323 }
324 Some(FileDescriptor::Duplicate(raw_fd)) => {
325 let dup_fd = unsafe { libc::dup(*raw_fd) };
327 if dup_fd >= 0 {
328 let file = unsafe { File::from_raw_fd(dup_fd) };
329 Some(Stdio::from(file))
330 } else {
331 None
332 }
333 }
334 Some(FileDescriptor::Closed) | None => None,
335 }
336 }
337
338 pub fn get_raw_fd(&self, fd_num: i32) -> Option<RawFd> {
347 match self.fds.get(&fd_num) {
348 Some(FileDescriptor::File(file)) => Some(file.as_raw_fd()),
349 Some(FileDescriptor::Duplicate(raw_fd)) => Some(*raw_fd),
350 Some(FileDescriptor::Closed) => None,
351 None => {
352 if fd_num >= 0 && fd_num <= 2 {
354 Some(fd_num as RawFd)
355 } else {
356 None
357 }
358 }
359 }
360 }
361
362 pub fn is_open(&self, fd_num: i32) -> bool {
371 matches!(
372 self.fds.get(&fd_num),
373 Some(FileDescriptor::File(_)) | Some(FileDescriptor::Duplicate(_))
374 )
375 }
376
377 pub fn is_closed(&self, fd_num: i32) -> bool {
386 matches!(self.fds.get(&fd_num), Some(FileDescriptor::Closed))
387 }
388
389 pub fn clear(&mut self) {
391 self.fds.clear();
392 self.saved_fds.clear();
393 }
394}
395
396impl Default for FileDescriptorTable {
397 fn default() -> Self {
398 Self::new()
399 }
400}
401
402#[derive(Debug, Clone)]
403pub struct ColorScheme {
404 pub prompt: String,
406 pub error: String,
408 pub success: String,
410 pub builtin: String,
412 pub directory: String,
414}
415
416impl Default for ColorScheme {
417 fn default() -> Self {
418 Self {
419 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(), }
425 }
426}
427
428#[derive(Debug, Clone)]
429pub struct ShellState {
430 pub variables: HashMap<String, String>,
432 pub exported: HashSet<String>,
434 pub last_exit_code: i32,
436 pub shell_pid: u32,
438 pub script_name: String,
440 pub dir_stack: Vec<String>,
442 pub aliases: HashMap<String, String>,
444 pub colors_enabled: bool,
446 pub color_scheme: ColorScheme,
448 pub positional_params: Vec<String>,
450 pub functions: HashMap<String, Ast>,
452 pub local_vars: Vec<HashMap<String, String>>,
454 pub function_depth: usize,
456 pub max_recursion_depth: usize,
458 pub returning: bool,
460 pub return_value: Option<i32>,
462 pub capture_output: Option<Rc<RefCell<Vec<u8>>>>,
464 pub condensed_cwd: bool,
466 pub trap_handlers: Arc<Mutex<HashMap<String, String>>>,
468 pub exit_trap_executed: bool,
470 pub exit_requested: bool,
472 pub exit_code: i32,
474 #[allow(dead_code)]
477 pub pending_signals: bool,
478 pub pending_heredoc_content: Option<String>,
480 pub collecting_heredoc: Option<(String, String, String)>, pub fd_table: Rc<RefCell<FileDescriptorTable>>,
484 pub subshell_depth: usize,
486 pub stdin_override: Option<RawFd>,
488}
489
490impl ShellState {
491 pub fn new() -> Self {
492 let shell_pid = std::process::id();
493
494 let no_color = env::var("NO_COLOR").is_ok();
496
497 let rush_colors = env::var("RUSH_COLORS")
499 .map(|v| v.to_lowercase())
500 .unwrap_or_else(|_| "auto".to_string());
501
502 let colors_enabled = match rush_colors.as_str() {
503 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
504 "0" | "false" | "off" | "disable" => false,
505 "auto" => !no_color && std::io::stdout().is_terminal(),
506 _ => !no_color && std::io::stdout().is_terminal(),
507 };
508
509 let rush_condensed = env::var("RUSH_CONDENSED")
511 .map(|v| v.to_lowercase())
512 .unwrap_or_else(|_| "true".to_string());
513
514 let condensed_cwd = match rush_condensed.as_str() {
515 "1" | "true" | "on" | "enable" => true,
516 "0" | "false" | "off" | "disable" => false,
517 _ => true, };
519
520 Self {
521 variables: HashMap::new(),
522 exported: HashSet::new(),
523 last_exit_code: 0,
524 shell_pid,
525 script_name: "rush".to_string(),
526 dir_stack: Vec::new(),
527 aliases: HashMap::new(),
528 colors_enabled,
529 color_scheme: ColorScheme::default(),
530 positional_params: Vec::new(),
531 functions: HashMap::new(),
532 local_vars: Vec::new(),
533 function_depth: 0,
534 max_recursion_depth: 500, returning: false,
536 return_value: None,
537 capture_output: None,
538 condensed_cwd,
539 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
540 exit_trap_executed: false,
541 exit_requested: false,
542 exit_code: 0,
543 pending_signals: false,
544 pending_heredoc_content: None,
545 collecting_heredoc: None,
546 fd_table: Rc::new(RefCell::new(FileDescriptorTable::new())),
547 subshell_depth: 0,
548 stdin_override: None,
549 }
550 }
551
552 pub fn get_var(&self, name: &str) -> Option<String> {
554 match name {
556 "?" => Some(self.last_exit_code.to_string()),
557 "$" => Some(self.shell_pid.to_string()),
558 "0" => Some(self.script_name.clone()),
559 "*" => {
560 if self.positional_params.is_empty() {
562 Some("".to_string())
563 } else {
564 Some(self.positional_params.join(" "))
565 }
566 }
567 "@" => {
568 if self.positional_params.is_empty() {
570 Some("".to_string())
571 } else {
572 Some(self.positional_params.join(" "))
573 }
574 }
575 "#" => Some(self.positional_params.len().to_string()),
576 _ => {
577 if let Ok(index) = name.parse::<usize>()
579 && index > 0
580 && index <= self.positional_params.len()
581 {
582 return Some(self.positional_params[index - 1].clone());
583 }
584
585 for scope in self.local_vars.iter().rev() {
588 if let Some(value) = scope.get(name) {
589 return Some(value.clone());
590 }
591 }
592
593 if let Some(value) = self.variables.get(name) {
595 Some(value.clone())
596 } else {
597 env::var(name).ok()
599 }
600 }
601 }
602 }
603
604 pub fn set_var(&mut self, name: &str, value: String) {
606 for scope in self.local_vars.iter_mut().rev() {
609 if scope.contains_key(name) {
610 scope.insert(name.to_string(), value);
611 return;
612 }
613 }
614
615 self.variables.insert(name.to_string(), value);
617 }
618
619 pub fn unset_var(&mut self, name: &str) {
621 self.variables.remove(name);
622 self.exported.remove(name);
623 }
624
625 pub fn export_var(&mut self, name: &str) {
627 if self.variables.contains_key(name) {
628 self.exported.insert(name.to_string());
629 }
630 }
631
632 pub fn set_exported_var(&mut self, name: &str, value: String) {
634 self.set_var(name, value);
635 self.export_var(name);
636 }
637
638 pub fn get_env_for_child(&self) -> HashMap<String, String> {
640 let mut child_env = HashMap::new();
641
642 for (key, value) in env::vars() {
644 child_env.insert(key, value);
645 }
646
647 for var_name in &self.exported {
649 if let Some(value) = self.variables.get(var_name) {
650 child_env.insert(var_name.clone(), value.clone());
651 }
652 }
653
654 child_env
655 }
656
657 pub fn set_last_exit_code(&mut self, code: i32) {
659 self.last_exit_code = code;
660 }
661
662 pub fn set_script_name(&mut self, name: &str) {
664 self.script_name = name.to_string();
665 }
666
667 pub fn get_condensed_cwd(&self) -> String {
669 match env::current_dir() {
670 Ok(path) => {
671 let path_str = path.to_string_lossy();
672 let components: Vec<&str> = path_str.split('/').collect();
673 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
674 return "/".to_string();
675 }
676 let mut result = String::new();
677 for (i, comp) in components.iter().enumerate() {
678 if comp.is_empty() {
679 continue; }
681 if i == components.len() - 1 {
682 result.push('/');
683 result.push_str(comp);
684 } else {
685 result.push('/');
686 if let Some(first) = comp.chars().next() {
687 result.push(first);
688 }
689 }
690 }
691 if result.is_empty() {
692 "/".to_string()
693 } else {
694 result
695 }
696 }
697 Err(_) => "/?".to_string(), }
699 }
700
701 pub fn get_full_cwd(&self) -> String {
703 match env::current_dir() {
704 Ok(path) => path.to_string_lossy().to_string(),
705 Err(_) => "/?".to_string(), }
707 }
708
709 pub fn get_user_hostname(&self) -> String {
711 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
712
713 if let Ok(hostname) = env::var("HOSTNAME")
715 && !hostname.trim().is_empty()
716 {
717 return format!("{}@{}", user, hostname);
718 }
719
720 let hostname = match std::process::Command::new("hostname").output() {
722 Ok(output) if output.status.success() => {
723 String::from_utf8_lossy(&output.stdout).trim().to_string()
724 }
725 _ => "hostname".to_string(), };
727
728 if hostname != "hostname" {
730 unsafe {
731 env::set_var("HOSTNAME", &hostname);
732 }
733 }
734
735 format!("{}@{}", user, hostname)
736 }
737
738 pub fn get_prompt(&self) -> String {
740 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
741 let prompt_char = if user == "root" { "#" } else { "$" };
742 let cwd = if self.condensed_cwd {
743 self.get_condensed_cwd()
744 } else {
745 self.get_full_cwd()
746 };
747 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
748 }
749
750 pub fn set_alias(&mut self, name: &str, value: String) {
752 self.aliases.insert(name.to_string(), value);
753 }
754
755 pub fn get_alias(&self, name: &str) -> Option<&String> {
757 self.aliases.get(name)
758 }
759
760 pub fn remove_alias(&mut self, name: &str) {
762 self.aliases.remove(name);
763 }
764
765 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
767 &self.aliases
768 }
769
770 pub fn set_positional_params(&mut self, params: Vec<String>) {
772 self.positional_params = params;
773 }
774
775 #[allow(dead_code)]
777 pub fn get_positional_params(&self) -> &[String] {
778 &self.positional_params
779 }
780
781 pub fn shift_positional_params(&mut self, count: usize) {
783 if count > 0 {
784 for _ in 0..count {
785 if !self.positional_params.is_empty() {
786 self.positional_params.remove(0);
787 }
788 }
789 }
790 }
791
792 #[allow(dead_code)]
794 pub fn push_positional_param(&mut self, param: String) {
795 self.positional_params.push(param);
796 }
797
798 pub fn define_function(&mut self, name: String, body: Ast) {
800 self.functions.insert(name, body);
801 }
802
803 pub fn get_function(&self, name: &str) -> Option<&Ast> {
805 self.functions.get(name)
806 }
807
808 #[allow(dead_code)]
810 pub fn remove_function(&mut self, name: &str) {
811 self.functions.remove(name);
812 }
813
814 #[allow(dead_code)]
816 pub fn get_function_names(&self) -> Vec<&String> {
817 self.functions.keys().collect()
818 }
819
820 pub fn push_local_scope(&mut self) {
822 self.local_vars.push(HashMap::new());
823 }
824
825 pub fn pop_local_scope(&mut self) {
827 if !self.local_vars.is_empty() {
828 self.local_vars.pop();
829 }
830 }
831
832 pub fn set_local_var(&mut self, name: &str, value: String) {
834 if let Some(current_scope) = self.local_vars.last_mut() {
835 current_scope.insert(name.to_string(), value);
836 } else {
837 self.set_var(name, value);
839 }
840 }
841
842 pub fn enter_function(&mut self) {
844 self.function_depth += 1;
845 if self.function_depth > self.local_vars.len() {
846 self.push_local_scope();
847 }
848 }
849
850 pub fn exit_function(&mut self) {
852 if self.function_depth > 0 {
853 self.function_depth -= 1;
854 if self.function_depth == self.local_vars.len() - 1 {
855 self.pop_local_scope();
856 }
857 }
858 }
859
860 pub fn set_return(&mut self, value: i32) {
862 self.returning = true;
863 self.return_value = Some(value);
864 }
865
866 pub fn clear_return(&mut self) {
868 self.returning = false;
869 self.return_value = None;
870 }
871
872 pub fn is_returning(&self) -> bool {
874 self.returning
875 }
876
877 pub fn get_return_value(&self) -> Option<i32> {
879 self.return_value
880 }
881
882 pub fn set_trap(&mut self, signal: &str, command: String) {
884 if let Ok(mut handlers) = self.trap_handlers.lock() {
885 handlers.insert(signal.to_uppercase(), command);
886 }
887 }
888
889 pub fn get_trap(&self, signal: &str) -> Option<String> {
891 if let Ok(handlers) = self.trap_handlers.lock() {
892 handlers.get(&signal.to_uppercase()).cloned()
893 } else {
894 None
895 }
896 }
897
898 pub fn remove_trap(&mut self, signal: &str) {
900 if let Ok(mut handlers) = self.trap_handlers.lock() {
901 handlers.remove(&signal.to_uppercase());
902 }
903 }
904
905 pub fn get_all_traps(&self) -> HashMap<String, String> {
907 if let Ok(handlers) = self.trap_handlers.lock() {
908 handlers.clone()
909 } else {
910 HashMap::new()
911 }
912 }
913
914 #[allow(dead_code)]
916 pub fn clear_traps(&mut self) {
917 if let Ok(mut handlers) = self.trap_handlers.lock() {
918 handlers.clear();
919 }
920 }
921}
922
923pub fn enqueue_signal(signal_name: &str, signal_number: i32) {
926 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
927 if queue.len() >= MAX_SIGNAL_QUEUE_SIZE {
929 queue.pop_front();
930 eprintln!("Warning: Signal queue overflow, dropping oldest signal");
931 }
932
933 queue.push_back(SignalEvent::new(signal_name.to_string(), signal_number));
934 }
935}
936
937pub fn process_pending_signals(shell_state: &mut ShellState) {
940 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
942 while let Some(signal_event) = queue.pop_front() {
944 if let Some(trap_cmd) = shell_state.get_trap(&signal_event.signal_name)
946 && !trap_cmd.is_empty()
947 {
948 if shell_state.colors_enabled {
950 eprintln!(
951 "{}Signal {} (signal {}) received at {:?}\x1b[0m",
952 shell_state.color_scheme.builtin,
953 signal_event.signal_name,
954 signal_event.signal_number,
955 signal_event.timestamp
956 );
957 } else {
958 eprintln!(
959 "Signal {} (signal {}) received at {:?}",
960 signal_event.signal_name,
961 signal_event.signal_number,
962 signal_event.timestamp
963 );
964 }
965
966 crate::executor::execute_trap_handler(&trap_cmd, shell_state);
969 }
970 }
971 }
972}
973
974impl Default for ShellState {
975 fn default() -> Self {
976 Self::new()
977 }
978}
979
980#[cfg(test)]
981mod tests {
982 use super::*;
983 use std::sync::Mutex;
984
985 static FILE_LOCK: Mutex<()> = Mutex::new(());
987
988 #[test]
989 fn test_shell_state_basic() {
990 let mut state = ShellState::new();
991 state.set_var("TEST_VAR", "test_value".to_string());
992 assert_eq!(state.get_var("TEST_VAR"), Some("test_value".to_string()));
993 }
994
995 #[test]
996 fn test_special_variables() {
997 let mut state = ShellState::new();
998 state.set_last_exit_code(42);
999 state.set_script_name("test_script");
1000
1001 assert_eq!(state.get_var("?"), Some("42".to_string()));
1002 assert_eq!(state.get_var("$"), Some(state.shell_pid.to_string()));
1003 assert_eq!(state.get_var("0"), Some("test_script".to_string()));
1004 }
1005
1006 #[test]
1007 fn test_export_variable() {
1008 let mut state = ShellState::new();
1009 state.set_var("EXPORT_VAR", "export_value".to_string());
1010 state.export_var("EXPORT_VAR");
1011
1012 let child_env = state.get_env_for_child();
1013 assert_eq!(
1014 child_env.get("EXPORT_VAR"),
1015 Some(&"export_value".to_string())
1016 );
1017 }
1018
1019 #[test]
1020 fn test_unset_variable() {
1021 let mut state = ShellState::new();
1022 state.set_var("UNSET_VAR", "value".to_string());
1023 state.export_var("UNSET_VAR");
1024
1025 assert!(state.variables.contains_key("UNSET_VAR"));
1026 assert!(state.exported.contains("UNSET_VAR"));
1027
1028 state.unset_var("UNSET_VAR");
1029
1030 assert!(!state.variables.contains_key("UNSET_VAR"));
1031 assert!(!state.exported.contains("UNSET_VAR"));
1032 }
1033
1034 #[test]
1035 fn test_get_user_hostname() {
1036 let state = ShellState::new();
1037 let user_hostname = state.get_user_hostname();
1038 assert!(user_hostname.contains('@'));
1040 }
1041
1042 #[test]
1043 fn test_get_prompt() {
1044 let state = ShellState::new();
1045 let prompt = state.get_prompt();
1046 assert!(prompt.ends_with(" $ "));
1048 assert!(prompt.contains('@'));
1049 }
1050
1051 #[test]
1052 fn test_positional_parameters() {
1053 let mut state = ShellState::new();
1054 state.set_positional_params(vec![
1055 "arg1".to_string(),
1056 "arg2".to_string(),
1057 "arg3".to_string(),
1058 ]);
1059
1060 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1061 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1062 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1063 assert_eq!(state.get_var("4"), None);
1064 assert_eq!(state.get_var("#"), Some("3".to_string()));
1065 assert_eq!(state.get_var("*"), Some("arg1 arg2 arg3".to_string()));
1066 assert_eq!(state.get_var("@"), Some("arg1 arg2 arg3".to_string()));
1067 }
1068
1069 #[test]
1070 fn test_positional_parameters_empty() {
1071 let mut state = ShellState::new();
1072 state.set_positional_params(vec![]);
1073
1074 assert_eq!(state.get_var("1"), None);
1075 assert_eq!(state.get_var("#"), Some("0".to_string()));
1076 assert_eq!(state.get_var("*"), Some("".to_string()));
1077 assert_eq!(state.get_var("@"), Some("".to_string()));
1078 }
1079
1080 #[test]
1081 fn test_shift_positional_params() {
1082 let mut state = ShellState::new();
1083 state.set_positional_params(vec![
1084 "arg1".to_string(),
1085 "arg2".to_string(),
1086 "arg3".to_string(),
1087 ]);
1088
1089 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1090 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1091 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1092
1093 state.shift_positional_params(1);
1094
1095 assert_eq!(state.get_var("1"), Some("arg2".to_string()));
1096 assert_eq!(state.get_var("2"), Some("arg3".to_string()));
1097 assert_eq!(state.get_var("3"), None);
1098 assert_eq!(state.get_var("#"), Some("2".to_string()));
1099
1100 state.shift_positional_params(2);
1101
1102 assert_eq!(state.get_var("1"), None);
1103 assert_eq!(state.get_var("#"), Some("0".to_string()));
1104 }
1105
1106 #[test]
1107 fn test_push_positional_param() {
1108 let mut state = ShellState::new();
1109 state.set_positional_params(vec!["arg1".to_string()]);
1110
1111 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1112 assert_eq!(state.get_var("#"), Some("1".to_string()));
1113
1114 state.push_positional_param("arg2".to_string());
1115
1116 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1117 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1118 assert_eq!(state.get_var("#"), Some("2".to_string()));
1119 }
1120
1121 #[test]
1122 fn test_local_variable_scoping() {
1123 let mut state = ShellState::new();
1124
1125 state.set_var("global_var", "global_value".to_string());
1127 assert_eq!(
1128 state.get_var("global_var"),
1129 Some("global_value".to_string())
1130 );
1131
1132 state.push_local_scope();
1134
1135 state.set_local_var("global_var", "local_value".to_string());
1137 assert_eq!(state.get_var("global_var"), Some("local_value".to_string()));
1138
1139 state.set_local_var("local_var", "local_only".to_string());
1141 assert_eq!(state.get_var("local_var"), Some("local_only".to_string()));
1142
1143 state.pop_local_scope();
1145
1146 assert_eq!(
1148 state.get_var("global_var"),
1149 Some("global_value".to_string())
1150 );
1151 assert_eq!(state.get_var("local_var"), None);
1152 }
1153
1154 #[test]
1155 fn test_nested_local_scopes() {
1156 let mut state = ShellState::new();
1157
1158 state.set_var("test_var", "global".to_string());
1160
1161 state.push_local_scope();
1163 state.set_local_var("test_var", "level1".to_string());
1164 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1165
1166 state.push_local_scope();
1168 state.set_local_var("test_var", "level2".to_string());
1169 assert_eq!(state.get_var("test_var"), Some("level2".to_string()));
1170
1171 state.pop_local_scope();
1173 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1174
1175 state.pop_local_scope();
1177 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1178 }
1179
1180 #[test]
1181 fn test_variable_set_in_local_scope() {
1182 let mut state = ShellState::new();
1183
1184 state.set_var("test_var", "global".to_string());
1186 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1187
1188 state.push_local_scope();
1190 state.set_local_var("test_var", "local".to_string());
1191 assert_eq!(state.get_var("test_var"), Some("local".to_string()));
1192
1193 state.pop_local_scope();
1195 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1196 }
1197
1198 #[test]
1199 fn test_condensed_cwd_environment_variable() {
1200 let state = ShellState::new();
1202 assert!(state.condensed_cwd);
1203
1204 unsafe {
1206 env::set_var("RUSH_CONDENSED", "true");
1207 }
1208 let state = ShellState::new();
1209 assert!(state.condensed_cwd);
1210
1211 unsafe {
1213 env::set_var("RUSH_CONDENSED", "false");
1214 }
1215 let state = ShellState::new();
1216 assert!(!state.condensed_cwd);
1217
1218 unsafe {
1220 env::remove_var("RUSH_CONDENSED");
1221 }
1222 }
1223
1224 #[test]
1225 fn test_get_full_cwd() {
1226 let state = ShellState::new();
1227 let full_cwd = state.get_full_cwd();
1228 assert!(!full_cwd.is_empty());
1229 assert!(full_cwd.contains('/') || full_cwd.contains('\\'));
1231 }
1232
1233 #[test]
1234 fn test_prompt_with_condensed_setting() {
1235 let mut state = ShellState::new();
1236
1237 assert!(state.condensed_cwd);
1239 let prompt_condensed = state.get_prompt();
1240 assert!(prompt_condensed.contains('@'));
1241
1242 state.condensed_cwd = false;
1244 let prompt_full = state.get_prompt();
1245 assert!(prompt_full.contains('@'));
1246
1247 assert!(prompt_condensed.ends_with("$ ") || prompt_condensed.ends_with("# "));
1249 assert!(prompt_full.ends_with("$ ") || prompt_full.ends_with("# "));
1250 }
1251
1252 #[test]
1255 fn test_fd_table_creation() {
1256 let fd_table = FileDescriptorTable::new();
1257 assert!(!fd_table.is_open(0));
1258 assert!(!fd_table.is_open(1));
1259 assert!(!fd_table.is_open(2));
1260 }
1261
1262 #[test]
1263 fn test_fd_table_open_file() {
1264 let mut fd_table = FileDescriptorTable::new();
1265
1266 let temp_file = "/tmp/rush_test_fd_open.txt";
1268 std::fs::write(temp_file, "test content").unwrap();
1269
1270 let result = fd_table.open_fd(3, temp_file, true, false, false, false);
1272 assert!(result.is_ok());
1273 assert!(fd_table.is_open(3));
1274
1275 let _ = std::fs::remove_file(temp_file);
1277 }
1278
1279 #[test]
1280 fn test_fd_table_open_file_for_writing() {
1281 let mut fd_table = FileDescriptorTable::new();
1282
1283 let temp_file = "/tmp/rush_test_fd_write.txt";
1285
1286 let result = fd_table.open_fd(4, temp_file, false, true, false, true);
1288 assert!(result.is_ok());
1289 assert!(fd_table.is_open(4));
1290
1291 let _ = std::fs::remove_file(temp_file);
1293 }
1294
1295 #[test]
1296 fn test_fd_table_invalid_fd_number() {
1297 let mut fd_table = FileDescriptorTable::new();
1298
1299 let result = fd_table.open_fd(-1, "/tmp/test.txt", true, false, false, false);
1301 assert!(result.is_err());
1302 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1303
1304 let result = fd_table.open_fd(1025, "/tmp/test.txt", true, false, false, false);
1305 assert!(result.is_err());
1306 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1307 }
1308
1309 #[test]
1310 fn test_fd_table_duplicate_fd() {
1311 let mut fd_table = FileDescriptorTable::new();
1312
1313 let temp_file = "/tmp/rush_test_fd_dup.txt";
1315 std::fs::write(temp_file, "test content").unwrap();
1316
1317 fd_table
1319 .open_fd(3, temp_file, true, false, false, false)
1320 .unwrap();
1321 assert!(fd_table.is_open(3));
1322
1323 let result = fd_table.duplicate_fd(3, 4);
1325 assert!(result.is_ok());
1326 assert!(fd_table.is_open(4));
1327
1328 let _ = std::fs::remove_file(temp_file);
1330 }
1331
1332 #[test]
1333 fn test_fd_table_duplicate_to_self() {
1334 let mut fd_table = FileDescriptorTable::new();
1335
1336 let temp_file = "/tmp/rush_test_fd_dup_self.txt";
1338 std::fs::write(temp_file, "test content").unwrap();
1339
1340 fd_table
1342 .open_fd(3, temp_file, true, false, false, false)
1343 .unwrap();
1344
1345 let result = fd_table.duplicate_fd(3, 3);
1347 assert!(result.is_ok());
1348 assert!(fd_table.is_open(3));
1349
1350 let _ = std::fs::remove_file(temp_file);
1352 }
1353
1354 #[test]
1355 fn test_fd_table_duplicate_closed_fd() {
1356 let mut fd_table = FileDescriptorTable::new();
1357
1358 let result = fd_table.duplicate_fd(3, 4);
1360 assert!(result.is_err());
1361 assert!(result.unwrap_err().contains("not open"));
1362 }
1363
1364 #[test]
1365 fn test_fd_table_close_fd() {
1366 let mut fd_table = FileDescriptorTable::new();
1367
1368 let temp_file = "/tmp/rush_test_fd_close.txt";
1370 std::fs::write(temp_file, "test content").unwrap();
1371
1372 fd_table
1374 .open_fd(3, temp_file, true, false, false, false)
1375 .unwrap();
1376 assert!(fd_table.is_open(3));
1377
1378 let result = fd_table.close_fd(3);
1380 assert!(result.is_ok());
1381 assert!(fd_table.is_closed(3));
1382 assert!(!fd_table.is_open(3));
1383
1384 let _ = std::fs::remove_file(temp_file);
1386 }
1387
1388 #[test]
1389 fn test_fd_table_save_and_restore() {
1390 let mut fd_table = FileDescriptorTable::new();
1391
1392 let result = fd_table.save_fd(0);
1394 assert!(result.is_ok());
1395
1396 let result = fd_table.restore_fd(0);
1398 assert!(result.is_ok());
1399 }
1400
1401 #[test]
1402 fn test_fd_table_save_all_and_restore_all() {
1403 let _lock = FILE_LOCK.lock().unwrap();
1404 let mut fd_table = FileDescriptorTable::new();
1405
1406 use std::time::{SystemTime, UNIX_EPOCH};
1408 let timestamp = SystemTime::now()
1409 .duration_since(UNIX_EPOCH)
1410 .unwrap()
1411 .as_nanos();
1412 let temp_file1 = format!("/tmp/rush_test_fd_save1_{}.txt", timestamp);
1413 let temp_file2 = format!("/tmp/rush_test_fd_save2_{}.txt", timestamp);
1414
1415 std::fs::write(&temp_file1, "test content 1").unwrap();
1416 std::fs::write(&temp_file2, "test content 2").unwrap();
1417
1418 let f1 = File::open(&temp_file1).unwrap();
1422 let f2 = File::open(&temp_file2).unwrap();
1423 unsafe {
1424 libc::dup2(f1.as_raw_fd(), 50);
1425 libc::dup2(f2.as_raw_fd(), 51);
1426 }
1427
1428 fd_table
1429 .open_fd(50, &temp_file1, true, false, false, false)
1430 .unwrap();
1431 fd_table
1432 .open_fd(51, &temp_file2, true, false, false, false)
1433 .unwrap();
1434
1435 let result = fd_table.save_all_fds();
1437 assert!(result.is_ok());
1438
1439 let result = fd_table.restore_all_fds();
1441 assert!(result.is_ok());
1442
1443 unsafe {
1445 libc::close(50);
1446 libc::close(51);
1447 }
1448 let _ = std::fs::remove_file(&temp_file1);
1449 let _ = std::fs::remove_file(&temp_file2);
1450 }
1451
1452 #[test]
1453 fn test_fd_table_clear() {
1454 let mut fd_table = FileDescriptorTable::new();
1455
1456 let temp_file = "/tmp/rush_test_fd_clear.txt";
1458 std::fs::write(temp_file, "test content").unwrap();
1459
1460 fd_table
1466 .open_fd(50, temp_file, true, false, false, false)
1467 .unwrap();
1468 assert!(fd_table.is_open(50));
1469
1470 fd_table.clear();
1472 assert!(!fd_table.is_open(3));
1473
1474 let _ = std::fs::remove_file(temp_file);
1476 }
1477
1478 #[test]
1479 fn test_fd_table_get_stdio() {
1480 let mut fd_table = FileDescriptorTable::new();
1481
1482 let temp_file = "/tmp/rush_test_fd_stdio.txt";
1484 std::fs::write(temp_file, "test content").unwrap();
1485
1486 fd_table
1488 .open_fd(3, temp_file, true, false, false, false)
1489 .unwrap();
1490
1491 let stdio = fd_table.get_stdio(3);
1493 assert!(stdio.is_some());
1494
1495 let stdio = fd_table.get_stdio(5);
1497 assert!(stdio.is_none());
1498
1499 let _ = std::fs::remove_file(temp_file);
1501 }
1502
1503 #[test]
1504 fn test_fd_table_multiple_operations() {
1505 let mut fd_table = FileDescriptorTable::new();
1506
1507 let temp_file1 = "/tmp/rush_test_fd_multi1.txt";
1509 let temp_file2 = "/tmp/rush_test_fd_multi2.txt";
1510 std::fs::write(temp_file1, "test content 1").unwrap();
1511 std::fs::write(temp_file2, "test content 2").unwrap();
1512
1513 fd_table
1515 .open_fd(3, temp_file1, true, false, false, false)
1516 .unwrap();
1517 assert!(fd_table.is_open(3));
1518
1519 fd_table.duplicate_fd(3, 4).unwrap();
1521 assert!(fd_table.is_open(4));
1522
1523 fd_table
1525 .open_fd(5, temp_file2, true, false, false, false)
1526 .unwrap();
1527 assert!(fd_table.is_open(5));
1528
1529 fd_table.close_fd(4).unwrap();
1531 assert!(fd_table.is_closed(4));
1532 assert!(!fd_table.is_open(4));
1533
1534 assert!(fd_table.is_open(3));
1536 assert!(fd_table.is_open(5));
1537
1538 let _ = std::fs::remove_file(temp_file1);
1540 let _ = std::fs::remove_file(temp_file2);
1541 }
1542
1543 #[test]
1544 fn test_shell_state_has_fd_table() {
1545 let state = ShellState::new();
1546 let fd_table = state.fd_table.borrow();
1547 assert!(!fd_table.is_open(3));
1548 }
1549
1550 #[test]
1551 fn test_shell_state_fd_table_operations() {
1552 let state = ShellState::new();
1553
1554 let temp_file = "/tmp/rush_test_state_fd.txt";
1556 std::fs::write(temp_file, "test content").unwrap();
1557
1558 {
1560 let mut fd_table = state.fd_table.borrow_mut();
1561 fd_table
1562 .open_fd(3, temp_file, true, false, false, false)
1563 .unwrap();
1564 }
1565
1566 {
1568 let fd_table = state.fd_table.borrow();
1569 assert!(fd_table.is_open(3));
1570 }
1571
1572 let _ = std::fs::remove_file(temp_file);
1574 }
1575}