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 create_new: bool,
111 ) -> Result<(), String> {
112 let mut opts = OpenOptions::new();
113 if create_new {
114 opts.create_new(true); } else if truncate {
116 opts.create(true).truncate(true);
117 }
118
119 if !(0..=1024).contains(&fd_num) {
121 return Err(format!("Invalid file descriptor number: {}", fd_num));
122 }
123
124 let file = OpenOptions::new()
126 .read(read)
127 .write(write)
128 .append(append)
129 .truncate(truncate)
130 .create(write || append)
131 .open(path)
132 .map_err(|e| format!("Cannot open {}: {}", path, e))?;
133
134 self.fds.insert(fd_num, FileDescriptor::File(file));
136 Ok(())
137 }
138
139 pub fn duplicate_fd(&mut self, source_fd: i32, target_fd: i32) -> Result<(), String> {
149 if !(0..=1024).contains(&source_fd) {
151 return Err(format!("Invalid source file descriptor: {}", source_fd));
152 }
153 if !(0..=1024).contains(&target_fd) {
154 return Err(format!("Invalid target file descriptor: {}", target_fd));
155 }
156
157 if source_fd == target_fd {
159 return Ok(());
160 }
161
162 let raw_fd = match self.get_raw_fd(source_fd) {
164 Some(fd) => fd,
165 None => {
166 return Err(format!(
167 "File descriptor {} is not open or is closed",
168 source_fd
169 ));
170 }
171 };
172
173 self.fds
175 .insert(target_fd, FileDescriptor::Duplicate(raw_fd));
176 Ok(())
177 }
178
179 pub fn close_fd(&mut self, fd_num: i32) -> Result<(), String> {
188 if !(0..=1024).contains(&fd_num) {
190 return Err(format!("Invalid file descriptor number: {}", fd_num));
191 }
192
193 self.fds.insert(fd_num, FileDescriptor::Closed);
195 Ok(())
196 }
197
198 pub fn save_fd(&mut self, fd_num: i32) -> Result<(), String> {
207 if !(0..=1024).contains(&fd_num) {
209 return Err(format!("Invalid file descriptor number: {}", fd_num));
210 }
211
212 let saved_fd = unsafe {
214 let raw_fd = fd_num as RawFd;
215 libc::dup(raw_fd)
216 };
217
218 if saved_fd < 0 {
219 return Err(format!("Failed to save file descriptor {}", fd_num));
220 }
221
222 self.saved_fds.insert(fd_num, saved_fd);
223 Ok(())
224 }
225
226 pub fn restore_fd(&mut self, fd_num: i32) -> Result<(), String> {
235 if !(0..=1024).contains(&fd_num) {
237 return Err(format!("Invalid file descriptor number: {}", fd_num));
238 }
239
240 if let Some(saved_fd) = self.saved_fds.remove(&fd_num) {
242 unsafe {
244 let result = libc::dup2(saved_fd, fd_num as RawFd);
245 libc::close(saved_fd); if result < 0 {
248 return Err(format!("Failed to restore file descriptor {}", fd_num));
249 }
250 }
251
252 self.fds.remove(&fd_num);
254 }
255
256 Ok(())
257 }
258
259 pub fn deep_clone(&self) -> Result<Self, String> {
262 let mut new_fds = HashMap::new();
263 for (fd, descriptor) in &self.fds {
264 new_fds.insert(*fd, descriptor.try_clone()?);
265 }
266
267 Ok(Self {
268 fds: new_fds,
269 saved_fds: self.saved_fds.clone(),
270 })
271 }
272
273 pub fn save_all_fds(&mut self) -> Result<(), String> {
279 let fd_nums: Vec<i32> = self.fds.keys().copied().collect();
281 for fd_num in fd_nums {
282 self.save_fd(fd_num)?;
283 }
284
285 for fd in 0..=2 {
288 if !self.fds.contains_key(&fd) {
289 let _ = self.save_fd(fd);
291 }
292 }
293 Ok(())
294 }
295
296 pub fn restore_all_fds(&mut self) -> Result<(), String> {
302 let fd_nums: Vec<i32> = self.saved_fds.keys().copied().collect();
304 for fd_num in fd_nums {
305 self.restore_fd(fd_num)?;
306 }
307 Ok(())
308 }
309
310 #[allow(dead_code)]
319 pub fn get_stdio(&self, fd_num: i32) -> Option<Stdio> {
320 match self.fds.get(&fd_num) {
321 Some(FileDescriptor::File(file)) => {
322 let raw_fd = file.as_raw_fd();
324 let dup_fd = unsafe { libc::dup(raw_fd) };
325 if dup_fd >= 0 {
326 let file = unsafe { File::from_raw_fd(dup_fd) };
327 Some(Stdio::from(file))
328 } else {
329 None
330 }
331 }
332 Some(FileDescriptor::Duplicate(raw_fd)) => {
333 let dup_fd = unsafe { libc::dup(*raw_fd) };
335 if dup_fd >= 0 {
336 let file = unsafe { File::from_raw_fd(dup_fd) };
337 Some(Stdio::from(file))
338 } else {
339 None
340 }
341 }
342 Some(FileDescriptor::Closed) | None => None,
343 }
344 }
345
346 pub fn get_raw_fd(&self, fd_num: i32) -> Option<RawFd> {
355 match self.fds.get(&fd_num) {
356 Some(FileDescriptor::File(file)) => Some(file.as_raw_fd()),
357 Some(FileDescriptor::Duplicate(raw_fd)) => Some(*raw_fd),
358 Some(FileDescriptor::Closed) => None,
359 None => {
360 if fd_num >= 0 && fd_num <= 2 {
362 Some(fd_num as RawFd)
363 } else {
364 None
365 }
366 }
367 }
368 }
369
370 pub fn is_open(&self, fd_num: i32) -> bool {
379 matches!(
380 self.fds.get(&fd_num),
381 Some(FileDescriptor::File(_)) | Some(FileDescriptor::Duplicate(_))
382 )
383 }
384
385 pub fn is_closed(&self, fd_num: i32) -> bool {
394 matches!(self.fds.get(&fd_num), Some(FileDescriptor::Closed))
395 }
396
397 pub fn clear(&mut self) {
399 self.fds.clear();
400 self.saved_fds.clear();
401 }
402}
403
404impl Default for FileDescriptorTable {
405 fn default() -> Self {
414 Self::new()
415 }
416}
417
418#[derive(Debug, Clone)]
420pub struct ShellOptions {
421 pub errexit: bool,
423
424 pub nounset: bool,
426
427 pub xtrace: bool,
429
430 pub verbose: bool,
432
433 pub noexec: bool,
435
436 pub noglob: bool,
438
439 pub noclobber: bool,
441
442 pub allexport: bool,
444
445 pub notify: bool,
447
448 pub ignoreeof: bool,
450
451 pub monitor: bool,
453}
454
455impl Default for ShellOptions {
456 fn default() -> Self {
466 Self {
467 errexit: false,
468 nounset: false,
469 xtrace: false,
470 verbose: false,
471 noexec: false,
472 noglob: false,
473 noclobber: false,
474 allexport: false,
475 notify: false,
476 ignoreeof: false,
477 monitor: false,
478 }
479 }
480}
481
482impl ShellOptions {
483 #[allow(dead_code)]
496 pub fn get_by_short_name(&self, name: char) -> Option<bool> {
497 match name {
498 'e' => Some(self.errexit),
499 'u' => Some(self.nounset),
500 'x' => Some(self.xtrace),
501 'v' => Some(self.verbose),
502 'n' => Some(self.noexec),
503 'f' => Some(self.noglob),
504 'C' => Some(self.noclobber),
505 'a' => Some(self.allexport),
506 'b' => Some(self.notify),
507 'm' => Some(self.monitor),
508 _ => None,
509 }
510 }
511
512 pub fn set_by_short_name(&mut self, name: char, value: bool) -> Result<(), String> {
536 match name {
537 'e' => {
538 self.errexit = value;
539 Ok(())
540 }
541 'u' => {
542 self.nounset = value;
543 Ok(())
544 }
545 'x' => {
546 self.xtrace = value;
547 Ok(())
548 }
549 'v' => {
550 self.verbose = value;
551 Ok(())
552 }
553 'n' => {
554 self.noexec = value;
555 Ok(())
556 }
557 'f' => {
558 self.noglob = value;
559 Ok(())
560 }
561 'C' => {
562 self.noclobber = value;
563 Ok(())
564 }
565 'a' => {
566 self.allexport = value;
567 Ok(())
568 }
569 'b' => {
570 self.notify = value;
571 Ok(())
572 }
573 'm' => {
574 self.monitor = value;
575 Ok(())
576 }
577 _ => Err(format!("Invalid option: -{}", name)),
578 }
579 }
580
581 #[allow(dead_code)]
600 pub fn get_by_long_name(&self, name: &str) -> Option<bool> {
601 match name {
602 "errexit" => Some(self.errexit),
603 "nounset" => Some(self.nounset),
604 "xtrace" => Some(self.xtrace),
605 "verbose" => Some(self.verbose),
606 "noexec" => Some(self.noexec),
607 "noglob" => Some(self.noglob),
608 "noclobber" => Some(self.noclobber),
609 "allexport" => Some(self.allexport),
610 "notify" => Some(self.notify),
611 "ignoreeof" => Some(self.ignoreeof),
612 "monitor" => Some(self.monitor),
613 _ => None,
614 }
615 }
616
617 pub fn set_by_long_name(&mut self, name: &str, value: bool) -> Result<(), String> {
633 match name {
634 "errexit" => {
635 self.errexit = value;
636 Ok(())
637 }
638 "nounset" => {
639 self.nounset = value;
640 Ok(())
641 }
642 "xtrace" => {
643 self.xtrace = value;
644 Ok(())
645 }
646 "verbose" => {
647 self.verbose = value;
648 Ok(())
649 }
650 "noexec" => {
651 self.noexec = value;
652 Ok(())
653 }
654 "noglob" => {
655 self.noglob = value;
656 Ok(())
657 }
658 "noclobber" => {
659 self.noclobber = value;
660 Ok(())
661 }
662 "allexport" => {
663 self.allexport = value;
664 Ok(())
665 }
666 "notify" => {
667 self.notify = value;
668 Ok(())
669 }
670 "ignoreeof" => {
671 self.ignoreeof = value;
672 Ok(())
673 }
674 "monitor" => {
675 self.monitor = value;
676 Ok(())
677 }
678 _ => Err(format!("Invalid option: {}", name)),
679 }
680 }
681
682 pub fn get_all_options(&self) -> Vec<(&'static str, char, bool)> {
697 vec![
698 ("allexport", 'a', self.allexport),
699 ("notify", 'b', self.notify),
700 ("noclobber", 'C', self.noclobber),
701 ("errexit", 'e', self.errexit),
702 ("noglob", 'f', self.noglob),
703 ("monitor", 'm', self.monitor),
704 ("noexec", 'n', self.noexec),
705 ("nounset", 'u', self.nounset),
706 ("verbose", 'v', self.verbose),
707 ("xtrace", 'x', self.xtrace),
708 ("ignoreeof", '\0', self.ignoreeof), ]
710 }
711}
712
713#[derive(Debug, Clone)]
714pub struct ColorScheme {
715 pub prompt: String,
717 pub error: String,
719 pub success: String,
721 pub builtin: String,
723 pub directory: String,
725}
726
727impl Default for ColorScheme {
728 fn default() -> Self {
729 Self {
730 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(), }
736 }
737}
738
739#[derive(Debug, Clone)]
740pub struct ShellState {
741 pub variables: HashMap<String, String>,
743 pub exported: HashSet<String>,
745 pub last_exit_code: i32,
747 pub shell_pid: u32,
749 pub script_name: String,
751 pub dir_stack: Vec<String>,
753 pub aliases: HashMap<String, String>,
755 pub colors_enabled: bool,
757 pub color_scheme: ColorScheme,
759 pub positional_params: Vec<String>,
761 pub functions: HashMap<String, Ast>,
763 pub local_vars: Vec<HashMap<String, String>>,
765 pub function_depth: usize,
767 pub max_recursion_depth: usize,
769 pub returning: bool,
771 pub return_value: Option<i32>,
773 pub loop_depth: usize,
775 pub breaking: bool,
777 pub break_level: usize,
779 pub continuing: bool,
781 pub continue_level: usize,
783 pub capture_output: Option<Rc<RefCell<Vec<u8>>>>,
785 pub condensed_cwd: bool,
787 pub trap_handlers: Arc<Mutex<HashMap<String, String>>>,
789 pub exit_trap_executed: bool,
791 pub exit_requested: bool,
793 pub exit_code: i32,
795 #[allow(dead_code)]
798 pub pending_signals: bool,
799 pub pending_heredoc_content: Option<String>,
801 pub collecting_heredoc: Option<(String, String, String)>, pub fd_table: Rc<RefCell<FileDescriptorTable>>,
805 pub subshell_depth: usize,
807 pub stdin_override: Option<RawFd>,
809 pub options: ShellOptions,
811 pub in_condition: bool,
813 pub in_logical_chain: bool,
815 pub in_negation: bool,
817 pub last_was_negation: bool,
819}
820
821impl ShellState {
822 pub fn new() -> Self {
838 let shell_pid = std::process::id();
839
840 let no_color = env::var("NO_COLOR").is_ok();
842
843 let rush_colors = env::var("RUSH_COLORS")
845 .map(|v| v.to_lowercase())
846 .unwrap_or_else(|_| "auto".to_string());
847
848 let colors_enabled = match rush_colors.as_str() {
849 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
850 "0" | "false" | "off" | "disable" => false,
851 "auto" => !no_color && std::io::stdout().is_terminal(),
852 _ => !no_color && std::io::stdout().is_terminal(),
853 };
854
855 let rush_condensed = env::var("RUSH_CONDENSED")
857 .map(|v| v.to_lowercase())
858 .unwrap_or_else(|_| "true".to_string());
859
860 let condensed_cwd = match rush_condensed.as_str() {
861 "1" | "true" | "on" | "enable" => true,
862 "0" | "false" | "off" | "disable" => false,
863 _ => true, };
865
866 Self {
867 variables: HashMap::new(),
868 exported: HashSet::new(),
869 last_exit_code: 0,
870 shell_pid,
871 script_name: "rush".to_string(),
872 dir_stack: Vec::new(),
873 aliases: HashMap::new(),
874 colors_enabled,
875 color_scheme: ColorScheme::default(),
876 positional_params: Vec::new(),
877 functions: HashMap::new(),
878 local_vars: Vec::new(),
879 function_depth: 0,
880 max_recursion_depth: 500, returning: false,
882 return_value: None,
883 loop_depth: 0,
884 breaking: false,
885 break_level: 0,
886 continuing: false,
887 continue_level: 0,
888 capture_output: None,
889 condensed_cwd,
890 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
891 exit_trap_executed: false,
892 exit_requested: false,
893 exit_code: 0,
894 pending_signals: false,
895 pending_heredoc_content: None,
896 collecting_heredoc: None,
897 fd_table: Rc::new(RefCell::new(FileDescriptorTable::new())),
898 subshell_depth: 0,
899 stdin_override: None,
900 options: ShellOptions::default(),
901 in_condition: false,
902 in_logical_chain: false,
903 in_negation: false,
904 last_was_negation: false,
905 }
906 }
907
908 pub fn get_var(&self, name: &str) -> Option<String> {
910 match name {
912 "?" => Some(self.last_exit_code.to_string()),
913 "$" => Some(self.shell_pid.to_string()),
914 "0" => Some(self.script_name.clone()),
915 "*" => {
916 if self.positional_params.is_empty() {
918 Some("".to_string())
919 } else {
920 Some(self.positional_params.join(" "))
921 }
922 }
923 "@" => {
924 if self.positional_params.is_empty() {
926 Some("".to_string())
927 } else {
928 Some(self.positional_params.join(" "))
929 }
930 }
931 "#" => Some(self.positional_params.len().to_string()),
932 _ => {
933 if let Ok(index) = name.parse::<usize>()
935 && index > 0
936 && index <= self.positional_params.len()
937 {
938 return Some(self.positional_params[index - 1].clone());
939 }
940
941 for scope in self.local_vars.iter().rev() {
944 if let Some(value) = scope.get(name) {
945 return Some(value.clone());
946 }
947 }
948
949 if let Some(value) = self.variables.get(name) {
951 Some(value.clone())
952 } else {
953 env::var(name).ok()
955 }
956 }
957 }
958 }
959
960 pub fn set_var(&mut self, name: &str, value: String) {
962 for scope in self.local_vars.iter_mut().rev() {
965 if scope.contains_key(name) {
966 scope.insert(name.to_string(), value);
967 return;
968 }
969 }
970
971 self.variables.insert(name.to_string(), value);
973 }
974
975 pub fn unset_var(&mut self, name: &str) {
977 self.variables.remove(name);
978 self.exported.remove(name);
979 }
980
981 pub fn export_var(&mut self, name: &str) {
983 if self.variables.contains_key(name) {
984 self.exported.insert(name.to_string());
985 }
986 }
987
988 pub fn set_exported_var(&mut self, name: &str, value: String) {
990 self.set_var(name, value);
991 self.export_var(name);
992 }
993
994 pub fn get_env_for_child(&self) -> HashMap<String, String> {
996 let mut child_env = HashMap::new();
997
998 for (key, value) in env::vars() {
1000 child_env.insert(key, value);
1001 }
1002
1003 for var_name in &self.exported {
1005 if let Some(value) = self.variables.get(var_name) {
1006 child_env.insert(var_name.clone(), value.clone());
1007 }
1008 }
1009
1010 child_env
1011 }
1012
1013 pub fn set_last_exit_code(&mut self, code: i32) {
1015 self.last_exit_code = code;
1016 }
1017
1018 pub fn set_script_name(&mut self, name: &str) {
1020 self.script_name = name.to_string();
1021 }
1022
1023 pub fn get_condensed_cwd(&self) -> String {
1025 match env::current_dir() {
1026 Ok(path) => {
1027 let path_str = path.to_string_lossy();
1028 let components: Vec<&str> = path_str.split('/').collect();
1029 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
1030 return "/".to_string();
1031 }
1032 let mut result = String::new();
1033 for (i, comp) in components.iter().enumerate() {
1034 if comp.is_empty() {
1035 continue; }
1037 if i == components.len() - 1 {
1038 result.push('/');
1039 result.push_str(comp);
1040 } else {
1041 result.push('/');
1042 if let Some(first) = comp.chars().next() {
1043 result.push(first);
1044 }
1045 }
1046 }
1047 if result.is_empty() {
1048 "/".to_string()
1049 } else {
1050 result
1051 }
1052 }
1053 Err(_) => "/?".to_string(), }
1055 }
1056
1057 pub fn get_full_cwd(&self) -> String {
1059 match env::current_dir() {
1060 Ok(path) => path.to_string_lossy().to_string(),
1061 Err(_) => "/?".to_string(), }
1063 }
1064
1065 pub fn get_user_hostname(&self) -> String {
1067 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
1068
1069 if let Ok(hostname) = env::var("HOSTNAME")
1071 && !hostname.trim().is_empty()
1072 {
1073 return format!("{}@{}", user, hostname);
1074 }
1075
1076 let hostname = match std::process::Command::new("hostname").output() {
1078 Ok(output) if output.status.success() => {
1079 String::from_utf8_lossy(&output.stdout).trim().to_string()
1080 }
1081 _ => "hostname".to_string(), };
1083
1084 if hostname != "hostname" {
1086 unsafe {
1087 env::set_var("HOSTNAME", &hostname);
1088 }
1089 }
1090
1091 format!("{}@{}", user, hostname)
1092 }
1093
1094 pub fn get_prompt(&self) -> String {
1096 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
1097 let prompt_char = if user == "root" { "#" } else { "$" };
1098 let cwd = if self.condensed_cwd {
1099 self.get_condensed_cwd()
1100 } else {
1101 self.get_full_cwd()
1102 };
1103 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
1104 }
1105
1106 pub fn set_alias(&mut self, name: &str, value: String) {
1108 self.aliases.insert(name.to_string(), value);
1109 }
1110
1111 pub fn get_alias(&self, name: &str) -> Option<&String> {
1113 self.aliases.get(name)
1114 }
1115
1116 pub fn remove_alias(&mut self, name: &str) {
1118 self.aliases.remove(name);
1119 }
1120
1121 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
1123 &self.aliases
1124 }
1125
1126 pub fn set_positional_params(&mut self, params: Vec<String>) {
1128 self.positional_params = params;
1129 }
1130
1131 #[allow(dead_code)]
1133 pub fn get_positional_params(&self) -> &[String] {
1134 &self.positional_params
1135 }
1136
1137 pub fn shift_positional_params(&mut self, count: usize) {
1139 if count > 0 {
1140 for _ in 0..count {
1141 if !self.positional_params.is_empty() {
1142 self.positional_params.remove(0);
1143 }
1144 }
1145 }
1146 }
1147
1148 #[allow(dead_code)]
1150 pub fn push_positional_param(&mut self, param: String) {
1151 self.positional_params.push(param);
1152 }
1153
1154 pub fn define_function(&mut self, name: String, body: Ast) {
1156 self.functions.insert(name, body);
1157 }
1158
1159 pub fn get_function(&self, name: &str) -> Option<&Ast> {
1161 self.functions.get(name)
1162 }
1163
1164 #[allow(dead_code)]
1166 pub fn remove_function(&mut self, name: &str) {
1167 self.functions.remove(name);
1168 }
1169
1170 #[allow(dead_code)]
1172 pub fn get_function_names(&self) -> Vec<&String> {
1173 self.functions.keys().collect()
1174 }
1175
1176 pub fn push_local_scope(&mut self) {
1178 self.local_vars.push(HashMap::new());
1179 }
1180
1181 pub fn pop_local_scope(&mut self) {
1183 if !self.local_vars.is_empty() {
1184 self.local_vars.pop();
1185 }
1186 }
1187
1188 pub fn set_local_var(&mut self, name: &str, value: String) {
1190 if let Some(current_scope) = self.local_vars.last_mut() {
1191 current_scope.insert(name.to_string(), value);
1192 } else {
1193 self.set_var(name, value);
1195 }
1196 }
1197
1198 pub fn enter_function(&mut self) {
1200 self.function_depth += 1;
1201 if self.function_depth > self.local_vars.len() {
1202 self.push_local_scope();
1203 }
1204 }
1205
1206 pub fn exit_function(&mut self) {
1208 if self.function_depth > 0 {
1209 self.function_depth -= 1;
1210 if self.function_depth == self.local_vars.len() - 1 {
1211 self.pop_local_scope();
1212 }
1213 }
1214 }
1215
1216 pub fn set_return(&mut self, value: i32) {
1218 self.returning = true;
1219 self.return_value = Some(value);
1220 }
1221
1222 pub fn clear_return(&mut self) {
1224 self.returning = false;
1225 self.return_value = None;
1226 }
1227
1228 pub fn is_returning(&self) -> bool {
1230 self.returning
1231 }
1232
1233 pub fn get_return_value(&self) -> Option<i32> {
1235 self.return_value
1236 }
1237
1238 pub fn enter_loop(&mut self) {
1240 self.loop_depth += 1;
1241 }
1242
1243 pub fn exit_loop(&mut self) {
1245 if self.loop_depth > 0 {
1246 self.loop_depth -= 1;
1247 }
1248 }
1249
1250 pub fn set_break(&mut self, level: usize) {
1252 self.breaking = true;
1253 self.break_level = level;
1254 }
1255
1256 pub fn clear_break(&mut self) {
1258 self.breaking = false;
1259 self.break_level = 0;
1260 }
1261
1262 pub fn is_breaking(&self) -> bool {
1264 self.breaking
1265 }
1266
1267 pub fn get_break_level(&self) -> usize {
1269 self.break_level
1270 }
1271
1272 pub fn decrement_break_level(&mut self) {
1274 if self.break_level > 0 {
1275 self.break_level -= 1;
1276 }
1277 if self.break_level == 0 {
1278 self.breaking = false;
1279 }
1280 }
1281
1282 pub fn set_continue(&mut self, level: usize) {
1284 self.continuing = true;
1285 self.continue_level = level;
1286 }
1287
1288 pub fn clear_continue(&mut self) {
1290 self.continuing = false;
1291 self.continue_level = 0;
1292 }
1293
1294 pub fn is_continuing(&self) -> bool {
1296 self.continuing
1297 }
1298
1299 pub fn get_continue_level(&self) -> usize {
1301 self.continue_level
1302 }
1303
1304 pub fn decrement_continue_level(&mut self) {
1306 if self.continue_level > 0 {
1307 self.continue_level -= 1;
1308 }
1309 if self.continue_level == 0 {
1310 self.continuing = false;
1311 }
1312 }
1313
1314 pub fn set_trap(&mut self, signal: &str, command: String) {
1316 if let Ok(mut handlers) = self.trap_handlers.lock() {
1317 handlers.insert(signal.to_uppercase(), command);
1318 }
1319 }
1320
1321 pub fn get_trap(&self, signal: &str) -> Option<String> {
1323 if let Ok(handlers) = self.trap_handlers.lock() {
1324 handlers.get(&signal.to_uppercase()).cloned()
1325 } else {
1326 None
1327 }
1328 }
1329
1330 pub fn remove_trap(&mut self, signal: &str) {
1332 if let Ok(mut handlers) = self.trap_handlers.lock() {
1333 handlers.remove(&signal.to_uppercase());
1334 }
1335 }
1336
1337 pub fn get_all_traps(&self) -> HashMap<String, String> {
1339 if let Ok(handlers) = self.trap_handlers.lock() {
1340 handlers.clone()
1341 } else {
1342 HashMap::new()
1343 }
1344 }
1345
1346 #[allow(dead_code)]
1348 pub fn clear_traps(&mut self) {
1349 if let Ok(mut handlers) = self.trap_handlers.lock() {
1350 handlers.clear();
1351 }
1352 }
1353}
1354
1355pub fn enqueue_signal(signal_name: &str, signal_number: i32) {
1358 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
1359 if queue.len() >= MAX_SIGNAL_QUEUE_SIZE {
1361 queue.pop_front();
1362 eprintln!("Warning: Signal queue overflow, dropping oldest signal");
1363 }
1364
1365 queue.push_back(SignalEvent::new(signal_name.to_string(), signal_number));
1366 }
1367}
1368
1369pub fn process_pending_signals(shell_state: &mut ShellState) {
1372 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
1374 while let Some(signal_event) = queue.pop_front() {
1376 if let Some(trap_cmd) = shell_state.get_trap(&signal_event.signal_name)
1378 && !trap_cmd.is_empty()
1379 {
1380 if shell_state.colors_enabled {
1382 eprintln!(
1383 "{}Signal {} (signal {}) received at {:?}\x1b[0m",
1384 shell_state.color_scheme.builtin,
1385 signal_event.signal_name,
1386 signal_event.signal_number,
1387 signal_event.timestamp
1388 );
1389 } else {
1390 eprintln!(
1391 "Signal {} (signal {}) received at {:?}",
1392 signal_event.signal_name,
1393 signal_event.signal_number,
1394 signal_event.timestamp
1395 );
1396 }
1397
1398 crate::executor::execute_trap_handler(&trap_cmd, shell_state);
1401 }
1402 }
1403 }
1404}
1405
1406impl Default for ShellState {
1407 fn default() -> Self {
1408 Self::new()
1409 }
1410}
1411
1412#[cfg(test)]
1413mod tests {
1414 use super::*;
1415 use std::sync::Mutex;
1416
1417 static FILE_LOCK: Mutex<()> = Mutex::new(());
1419
1420 #[test]
1421 fn test_shell_state_basic() {
1422 let mut state = ShellState::new();
1423 state.set_var("TEST_VAR", "test_value".to_string());
1424 assert_eq!(state.get_var("TEST_VAR"), Some("test_value".to_string()));
1425 }
1426
1427 #[test]
1428 fn test_special_variables() {
1429 let mut state = ShellState::new();
1430 state.set_last_exit_code(42);
1431 state.set_script_name("test_script");
1432
1433 assert_eq!(state.get_var("?"), Some("42".to_string()));
1434 assert_eq!(state.get_var("$"), Some(state.shell_pid.to_string()));
1435 assert_eq!(state.get_var("0"), Some("test_script".to_string()));
1436 }
1437
1438 #[test]
1439 fn test_export_variable() {
1440 let mut state = ShellState::new();
1441 state.set_var("EXPORT_VAR", "export_value".to_string());
1442 state.export_var("EXPORT_VAR");
1443
1444 let child_env = state.get_env_for_child();
1445 assert_eq!(
1446 child_env.get("EXPORT_VAR"),
1447 Some(&"export_value".to_string())
1448 );
1449 }
1450
1451 #[test]
1452 fn test_unset_variable() {
1453 let mut state = ShellState::new();
1454 state.set_var("UNSET_VAR", "value".to_string());
1455 state.export_var("UNSET_VAR");
1456
1457 assert!(state.variables.contains_key("UNSET_VAR"));
1458 assert!(state.exported.contains("UNSET_VAR"));
1459
1460 state.unset_var("UNSET_VAR");
1461
1462 assert!(!state.variables.contains_key("UNSET_VAR"));
1463 assert!(!state.exported.contains("UNSET_VAR"));
1464 }
1465
1466 #[test]
1467 fn test_get_user_hostname() {
1468 let state = ShellState::new();
1469 let user_hostname = state.get_user_hostname();
1470 assert!(user_hostname.contains('@'));
1472 }
1473
1474 #[test]
1475 fn test_get_prompt() {
1476 let state = ShellState::new();
1477 let prompt = state.get_prompt();
1478 assert!(prompt.ends_with(" $ "));
1480 assert!(prompt.contains('@'));
1481 }
1482
1483 #[test]
1484 fn test_positional_parameters() {
1485 let mut state = ShellState::new();
1486 state.set_positional_params(vec![
1487 "arg1".to_string(),
1488 "arg2".to_string(),
1489 "arg3".to_string(),
1490 ]);
1491
1492 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1493 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1494 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1495 assert_eq!(state.get_var("4"), None);
1496 assert_eq!(state.get_var("#"), Some("3".to_string()));
1497 assert_eq!(state.get_var("*"), Some("arg1 arg2 arg3".to_string()));
1498 assert_eq!(state.get_var("@"), Some("arg1 arg2 arg3".to_string()));
1499 }
1500
1501 #[test]
1502 fn test_positional_parameters_empty() {
1503 let mut state = ShellState::new();
1504 state.set_positional_params(vec![]);
1505
1506 assert_eq!(state.get_var("1"), None);
1507 assert_eq!(state.get_var("#"), Some("0".to_string()));
1508 assert_eq!(state.get_var("*"), Some("".to_string()));
1509 assert_eq!(state.get_var("@"), Some("".to_string()));
1510 }
1511
1512 #[test]
1513 fn test_shift_positional_params() {
1514 let mut state = ShellState::new();
1515 state.set_positional_params(vec![
1516 "arg1".to_string(),
1517 "arg2".to_string(),
1518 "arg3".to_string(),
1519 ]);
1520
1521 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1522 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1523 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1524
1525 state.shift_positional_params(1);
1526
1527 assert_eq!(state.get_var("1"), Some("arg2".to_string()));
1528 assert_eq!(state.get_var("2"), Some("arg3".to_string()));
1529 assert_eq!(state.get_var("3"), None);
1530 assert_eq!(state.get_var("#"), Some("2".to_string()));
1531
1532 state.shift_positional_params(2);
1533
1534 assert_eq!(state.get_var("1"), None);
1535 assert_eq!(state.get_var("#"), Some("0".to_string()));
1536 }
1537
1538 #[test]
1539 fn test_push_positional_param() {
1540 let mut state = ShellState::new();
1541 state.set_positional_params(vec!["arg1".to_string()]);
1542
1543 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1544 assert_eq!(state.get_var("#"), Some("1".to_string()));
1545
1546 state.push_positional_param("arg2".to_string());
1547
1548 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1549 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1550 assert_eq!(state.get_var("#"), Some("2".to_string()));
1551 }
1552
1553 #[test]
1554 fn test_local_variable_scoping() {
1555 let mut state = ShellState::new();
1556
1557 state.set_var("global_var", "global_value".to_string());
1559 assert_eq!(
1560 state.get_var("global_var"),
1561 Some("global_value".to_string())
1562 );
1563
1564 state.push_local_scope();
1566
1567 state.set_local_var("global_var", "local_value".to_string());
1569 assert_eq!(state.get_var("global_var"), Some("local_value".to_string()));
1570
1571 state.set_local_var("local_var", "local_only".to_string());
1573 assert_eq!(state.get_var("local_var"), Some("local_only".to_string()));
1574
1575 state.pop_local_scope();
1577
1578 assert_eq!(
1580 state.get_var("global_var"),
1581 Some("global_value".to_string())
1582 );
1583 assert_eq!(state.get_var("local_var"), None);
1584 }
1585
1586 #[test]
1587 fn test_nested_local_scopes() {
1588 let mut state = ShellState::new();
1589
1590 state.set_var("test_var", "global".to_string());
1592
1593 state.push_local_scope();
1595 state.set_local_var("test_var", "level1".to_string());
1596 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1597
1598 state.push_local_scope();
1600 state.set_local_var("test_var", "level2".to_string());
1601 assert_eq!(state.get_var("test_var"), Some("level2".to_string()));
1602
1603 state.pop_local_scope();
1605 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1606
1607 state.pop_local_scope();
1609 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1610 }
1611
1612 #[test]
1613 fn test_variable_set_in_local_scope() {
1614 let mut state = ShellState::new();
1615
1616 state.set_var("test_var", "global".to_string());
1618 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1619
1620 state.push_local_scope();
1622 state.set_local_var("test_var", "local".to_string());
1623 assert_eq!(state.get_var("test_var"), Some("local".to_string()));
1624
1625 state.pop_local_scope();
1627 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1628 }
1629
1630 #[test]
1631 fn test_condensed_cwd_environment_variable() {
1632 let state = ShellState::new();
1634 assert!(state.condensed_cwd);
1635
1636 unsafe {
1638 env::set_var("RUSH_CONDENSED", "true");
1639 }
1640 let state = ShellState::new();
1641 assert!(state.condensed_cwd);
1642
1643 unsafe {
1645 env::set_var("RUSH_CONDENSED", "false");
1646 }
1647 let state = ShellState::new();
1648 assert!(!state.condensed_cwd);
1649
1650 unsafe {
1652 env::remove_var("RUSH_CONDENSED");
1653 }
1654 }
1655
1656 #[test]
1657 fn test_get_full_cwd() {
1658 let state = ShellState::new();
1659 let full_cwd = state.get_full_cwd();
1660 assert!(!full_cwd.is_empty());
1661 assert!(full_cwd.contains('/') || full_cwd.contains('\\'));
1663 }
1664
1665 #[test]
1666 fn test_prompt_with_condensed_setting() {
1667 let mut state = ShellState::new();
1668
1669 assert!(state.condensed_cwd);
1671 let prompt_condensed = state.get_prompt();
1672 assert!(prompt_condensed.contains('@'));
1673
1674 state.condensed_cwd = false;
1676 let prompt_full = state.get_prompt();
1677 assert!(prompt_full.contains('@'));
1678
1679 assert!(prompt_condensed.ends_with("$ ") || prompt_condensed.ends_with("# "));
1681 assert!(prompt_full.ends_with("$ ") || prompt_full.ends_with("# "));
1682 }
1683
1684 #[test]
1687 fn test_fd_table_creation() {
1688 let fd_table = FileDescriptorTable::new();
1689 assert!(!fd_table.is_open(0));
1690 assert!(!fd_table.is_open(1));
1691 assert!(!fd_table.is_open(2));
1692 }
1693
1694 #[test]
1695 fn test_fd_table_open_file() {
1696 let mut fd_table = FileDescriptorTable::new();
1697
1698 let temp_file = "/tmp/rush_test_fd_open.txt";
1700 std::fs::write(temp_file, "test content").unwrap();
1701
1702 let result = fd_table.open_fd(3, temp_file, true, false, false, false, false);
1704 assert!(result.is_ok());
1705 assert!(fd_table.is_open(3));
1706
1707 let _ = std::fs::remove_file(temp_file);
1709 }
1710
1711 #[test]
1712 fn test_fd_table_open_file_for_writing() {
1713 let mut fd_table = FileDescriptorTable::new();
1714
1715 let temp_file = "/tmp/rush_test_fd_write.txt";
1717
1718 let result = fd_table.open_fd(4, temp_file, false, true, false, true, false);
1720 assert!(result.is_ok());
1721 assert!(fd_table.is_open(4));
1722
1723 let _ = std::fs::remove_file(temp_file);
1725 }
1726
1727 #[test]
1728 fn test_fd_table_invalid_fd_number() {
1729 let mut fd_table = FileDescriptorTable::new();
1730
1731 let result = fd_table.open_fd(-1, "/tmp/test.txt", true, false, false, false, false);
1733 assert!(result.is_err());
1734 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1735
1736 let result = fd_table.open_fd(1025, "/tmp/test.txt", true, false, false, false, false);
1737 assert!(result.is_err());
1738 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1739 }
1740
1741 #[test]
1742 fn test_fd_table_duplicate_fd() {
1743 let mut fd_table = FileDescriptorTable::new();
1744
1745 let temp_file = "/tmp/rush_test_fd_dup.txt";
1747 std::fs::write(temp_file, "test content").unwrap();
1748
1749 fd_table
1751 .open_fd(3, temp_file, true, false, false, false, false)
1752 .unwrap();
1753 assert!(fd_table.is_open(3));
1754
1755 let result = fd_table.duplicate_fd(3, 4);
1757 assert!(result.is_ok());
1758 assert!(fd_table.is_open(4));
1759
1760 let _ = std::fs::remove_file(temp_file);
1762 }
1763
1764 #[test]
1765 fn test_fd_table_duplicate_to_self() {
1766 let mut fd_table = FileDescriptorTable::new();
1767
1768 let temp_file = "/tmp/rush_test_fd_dup_self.txt";
1770 std::fs::write(temp_file, "test content").unwrap();
1771
1772 fd_table
1774 .open_fd(3, temp_file, true, false, false, false, false)
1775 .unwrap();
1776
1777 let result = fd_table.duplicate_fd(3, 3);
1779 assert!(result.is_ok());
1780 assert!(fd_table.is_open(3));
1781
1782 let _ = std::fs::remove_file(temp_file);
1784 }
1785
1786 #[test]
1787 fn test_fd_table_duplicate_closed_fd() {
1788 let mut fd_table = FileDescriptorTable::new();
1789
1790 let result = fd_table.duplicate_fd(3, 4);
1792 assert!(result.is_err());
1793 assert!(result.unwrap_err().contains("not open"));
1794 }
1795
1796 #[test]
1797 fn test_fd_table_close_fd() {
1798 let mut fd_table = FileDescriptorTable::new();
1799
1800 let temp_file = "/tmp/rush_test_fd_close.txt";
1802 std::fs::write(temp_file, "test content").unwrap();
1803
1804 fd_table
1806 .open_fd(3, temp_file, true, false, false, false, false)
1807 .unwrap();
1808 assert!(fd_table.is_open(3));
1809
1810 let result = fd_table.close_fd(3);
1812 assert!(result.is_ok());
1813 assert!(fd_table.is_closed(3));
1814 assert!(!fd_table.is_open(3));
1815
1816 let _ = std::fs::remove_file(temp_file);
1818 }
1819
1820 #[test]
1821 fn test_fd_table_save_and_restore() {
1822 let mut fd_table = FileDescriptorTable::new();
1823
1824 let result = fd_table.save_fd(0);
1826 assert!(result.is_ok());
1827
1828 let result = fd_table.restore_fd(0);
1830 assert!(result.is_ok());
1831 }
1832
1833 #[test]
1834 fn test_fd_table_save_all_and_restore_all() {
1835 let _lock = FILE_LOCK.lock().unwrap();
1836 let mut fd_table = FileDescriptorTable::new();
1837
1838 use std::time::{SystemTime, UNIX_EPOCH};
1840 let timestamp = SystemTime::now()
1841 .duration_since(UNIX_EPOCH)
1842 .unwrap()
1843 .as_nanos();
1844 let temp_file1 = format!("/tmp/rush_test_fd_save1_{}.txt", timestamp);
1845 let temp_file2 = format!("/tmp/rush_test_fd_save2_{}.txt", timestamp);
1846
1847 std::fs::write(&temp_file1, "test content 1").unwrap();
1848 std::fs::write(&temp_file2, "test content 2").unwrap();
1849
1850 let f1 = File::open(&temp_file1).unwrap();
1854 let f2 = File::open(&temp_file2).unwrap();
1855 unsafe {
1856 libc::dup2(f1.as_raw_fd(), 50);
1857 libc::dup2(f2.as_raw_fd(), 51);
1858 }
1859
1860 fd_table
1861 .open_fd(50, &temp_file1, true, false, false, false, false)
1862 .unwrap();
1863 fd_table
1864 .open_fd(51, &temp_file2, true, false, false, false, false)
1865 .unwrap();
1866
1867 let result = fd_table.save_all_fds();
1869 assert!(result.is_ok());
1870
1871 let result = fd_table.restore_all_fds();
1873 assert!(result.is_ok());
1874
1875 unsafe {
1877 libc::close(50);
1878 libc::close(51);
1879 }
1880 let _ = std::fs::remove_file(&temp_file1);
1881 let _ = std::fs::remove_file(&temp_file2);
1882 }
1883
1884 #[test]
1885 fn test_fd_table_clear() {
1886 let mut fd_table = FileDescriptorTable::new();
1887
1888 let temp_file = "/tmp/rush_test_fd_clear.txt";
1890 std::fs::write(temp_file, "test content").unwrap();
1891
1892 fd_table
1898 .open_fd(50, temp_file, true, false, false, false, false)
1899 .unwrap();
1900 assert!(fd_table.is_open(50));
1901
1902 fd_table.clear();
1904 assert!(!fd_table.is_open(3));
1905
1906 let _ = std::fs::remove_file(temp_file);
1908 }
1909
1910 #[test]
1911 fn test_fd_table_get_stdio() {
1912 let mut fd_table = FileDescriptorTable::new();
1913
1914 let temp_file = "/tmp/rush_test_fd_stdio.txt";
1916 std::fs::write(temp_file, "test content").unwrap();
1917
1918 fd_table
1920 .open_fd(3, temp_file, true, false, false, false, false)
1921 .unwrap();
1922
1923 let stdio = fd_table.get_stdio(3);
1925 assert!(stdio.is_some());
1926
1927 let stdio = fd_table.get_stdio(5);
1929 assert!(stdio.is_none());
1930
1931 let _ = std::fs::remove_file(temp_file);
1933 }
1934
1935 #[test]
1936 fn test_fd_table_multiple_operations() {
1937 let mut fd_table = FileDescriptorTable::new();
1938
1939 let temp_file1 = "/tmp/rush_test_fd_multi1.txt";
1941 let temp_file2 = "/tmp/rush_test_fd_multi2.txt";
1942 std::fs::write(temp_file1, "test content 1").unwrap();
1943 std::fs::write(temp_file2, "test content 2").unwrap();
1944
1945 fd_table
1947 .open_fd(3, temp_file1, true, false, false, false, false)
1948 .unwrap();
1949 assert!(fd_table.is_open(3));
1950
1951 fd_table.duplicate_fd(3, 4).unwrap();
1953 assert!(fd_table.is_open(4));
1954
1955 fd_table
1957 .open_fd(5, temp_file2, true, false, false, false, false)
1958 .unwrap();
1959 assert!(fd_table.is_open(5));
1960
1961 fd_table.close_fd(4).unwrap();
1963 assert!(fd_table.is_closed(4));
1964 assert!(!fd_table.is_open(4));
1965
1966 assert!(fd_table.is_open(3));
1968 assert!(fd_table.is_open(5));
1969
1970 let _ = std::fs::remove_file(temp_file1);
1972 let _ = std::fs::remove_file(temp_file2);
1973 }
1974
1975 #[test]
1976 fn test_shell_state_has_fd_table() {
1977 let state = ShellState::new();
1978 let fd_table = state.fd_table.borrow();
1979 assert!(!fd_table.is_open(3));
1980 }
1981
1982 #[test]
1983 fn test_shell_state_fd_table_operations() {
1984 let state = ShellState::new();
1985
1986 let temp_file = "/tmp/rush_test_state_fd.txt";
1988 std::fs::write(temp_file, "test content").unwrap();
1989
1990 {
1992 let mut fd_table = state.fd_table.borrow_mut();
1993 fd_table
1994 .open_fd(3, temp_file, true, false, false, false, false)
1995 .unwrap();
1996 }
1997
1998 {
2000 let fd_table = state.fd_table.borrow();
2001 assert!(fd_table.is_open(3));
2002 }
2003
2004 let _ = std::fs::remove_file(temp_file);
2006 }
2007
2008 #[test]
2011 fn test_shell_options_default() {
2012 let options = ShellOptions::default();
2013 assert!(!options.errexit);
2014 assert!(!options.nounset);
2015 assert!(!options.xtrace);
2016 assert!(!options.verbose);
2017 assert!(!options.noexec);
2018 assert!(!options.noglob);
2019 assert!(!options.noclobber);
2020 assert!(!options.allexport);
2021 assert!(!options.notify);
2022 assert!(!options.ignoreeof);
2023 assert!(!options.monitor);
2024 }
2025
2026 #[test]
2027 fn test_shell_options_get_by_short_name() {
2028 let mut options = ShellOptions::default();
2029 options.errexit = true;
2030 options.nounset = true;
2031
2032 assert_eq!(options.get_by_short_name('e'), Some(true));
2033 assert_eq!(options.get_by_short_name('u'), Some(true));
2034 assert_eq!(options.get_by_short_name('x'), Some(false));
2035 assert_eq!(options.get_by_short_name('Z'), None);
2036 }
2037
2038 #[test]
2039 fn test_shell_options_set_by_short_name() {
2040 let mut options = ShellOptions::default();
2041
2042 assert!(options.set_by_short_name('e', true).is_ok());
2043 assert!(options.errexit);
2044
2045 assert!(options.set_by_short_name('u', true).is_ok());
2046 assert!(options.nounset);
2047
2048 assert!(options.set_by_short_name('x', true).is_ok());
2049 assert!(options.xtrace);
2050
2051 assert!(options.set_by_short_name('e', false).is_ok());
2052 assert!(!options.errexit);
2053
2054 assert!(options.set_by_short_name('Z', true).is_err());
2056 }
2057
2058 #[test]
2059 fn test_shell_options_get_by_long_name() {
2060 let mut options = ShellOptions::default();
2061 options.errexit = true;
2062 options.nounset = true;
2063
2064 assert_eq!(options.get_by_long_name("errexit"), Some(true));
2065 assert_eq!(options.get_by_long_name("nounset"), Some(true));
2066 assert_eq!(options.get_by_long_name("xtrace"), Some(false));
2067 assert_eq!(options.get_by_long_name("invalid"), None);
2068 }
2069
2070 #[test]
2071 fn test_shell_options_set_by_long_name() {
2072 let mut options = ShellOptions::default();
2073
2074 assert!(options.set_by_long_name("errexit", true).is_ok());
2075 assert!(options.errexit);
2076
2077 assert!(options.set_by_long_name("nounset", true).is_ok());
2078 assert!(options.nounset);
2079
2080 assert!(options.set_by_long_name("xtrace", true).is_ok());
2081 assert!(options.xtrace);
2082
2083 assert!(options.set_by_long_name("errexit", false).is_ok());
2084 assert!(!options.errexit);
2085
2086 assert!(options.set_by_long_name("invalid", true).is_err());
2088 }
2089
2090 #[test]
2091 fn test_shell_options_all_short_options() {
2092 let mut options = ShellOptions::default();
2093
2094 let short_opts = vec!['e', 'u', 'x', 'v', 'n', 'f', 'C', 'a', 'b', 'm'];
2096 for opt in short_opts {
2097 assert!(options.set_by_short_name(opt, true).is_ok());
2098 assert_eq!(options.get_by_short_name(opt), Some(true));
2099 assert!(options.set_by_short_name(opt, false).is_ok());
2100 assert_eq!(options.get_by_short_name(opt), Some(false));
2101 }
2102 }
2103
2104 #[test]
2105 fn test_shell_options_all_long_options() {
2106 let mut options = ShellOptions::default();
2107
2108 let long_opts = vec![
2110 "errexit",
2111 "nounset",
2112 "xtrace",
2113 "verbose",
2114 "noexec",
2115 "noglob",
2116 "noclobber",
2117 "allexport",
2118 "notify",
2119 "ignoreeof",
2120 "monitor",
2121 ];
2122 for opt in long_opts {
2123 assert!(options.set_by_long_name(opt, true).is_ok());
2124 assert_eq!(options.get_by_long_name(opt), Some(true));
2125 assert!(options.set_by_long_name(opt, false).is_ok());
2126 assert_eq!(options.get_by_long_name(opt), Some(false));
2127 }
2128 }
2129
2130 #[test]
2131 fn test_shell_options_get_all_options() {
2132 let mut options = ShellOptions::default();
2133 options.errexit = true;
2134 options.xtrace = true;
2135
2136 let all_options = options.get_all_options();
2137
2138 assert_eq!(all_options.len(), 11);
2140
2141 let errexit_opt = all_options.iter().find(|(name, _, _)| *name == "errexit");
2143 assert!(errexit_opt.is_some());
2144 assert_eq!(errexit_opt.unwrap().2, true);
2145
2146 let xtrace_opt = all_options.iter().find(|(name, _, _)| *name == "xtrace");
2148 assert!(xtrace_opt.is_some());
2149 assert_eq!(xtrace_opt.unwrap().2, true);
2150
2151 let nounset_opt = all_options.iter().find(|(name, _, _)| *name == "nounset");
2153 assert!(nounset_opt.is_some());
2154 assert_eq!(nounset_opt.unwrap().2, false);
2155 }
2156
2157 #[test]
2158 fn test_shell_state_has_options() {
2159 let state = ShellState::new();
2160 assert!(!state.options.errexit);
2161 assert!(!state.options.nounset);
2162 assert!(!state.options.xtrace);
2163 }
2164
2165 #[test]
2166 fn test_shell_state_options_modification() {
2167 let mut state = ShellState::new();
2168
2169 state.options.errexit = true;
2170 assert!(state.options.errexit);
2171
2172 state.options.set_by_short_name('u', true).unwrap();
2173 assert!(state.options.nounset);
2174
2175 state.options.set_by_long_name("xtrace", true).unwrap();
2176 assert!(state.options.xtrace);
2177 }
2178
2179 #[test]
2180 fn test_shell_options_error_messages() {
2181 let mut options = ShellOptions::default();
2182
2183 let result = options.set_by_short_name('Z', true);
2184 assert!(result.is_err());
2185 assert!(result.unwrap_err().contains("Invalid option: -Z"));
2186
2187 let result = options.set_by_long_name("invalid_option", true);
2188 assert!(result.is_err());
2189 assert!(
2190 result
2191 .unwrap_err()
2192 .contains("Invalid option: invalid_option")
2193 );
2194 }
2195
2196 #[test]
2197 fn test_shell_options_case_sensitivity() {
2198 let mut options = ShellOptions::default();
2199
2200 assert!(options.set_by_short_name('C', true).is_ok());
2202 assert!(options.noclobber);
2203 assert!(options.set_by_short_name('c', true).is_err());
2204 }
2205}