1use super::parser::Ast;
2use lazy_static::lazy_static;
3use std::cell::RefCell;
4use std::collections::{HashMap, HashSet, VecDeque};
5use std::env;
6use std::io::IsTerminal;
7use std::rc::Rc;
8use std::sync::{Arc, Mutex};
9use std::time::Instant;
10
11lazy_static! {
12 pub static ref SIGNAL_QUEUE: Arc<Mutex<VecDeque<SignalEvent>>> =
15 Arc::new(Mutex::new(VecDeque::new()));
16}
17
18const MAX_SIGNAL_QUEUE_SIZE: usize = 100;
20
21#[derive(Debug, Clone)]
23pub struct SignalEvent {
24 pub signal_name: String,
26 #[allow(dead_code)]
28 pub signal_number: i32,
29 #[allow(dead_code)]
31 pub timestamp: Instant,
32}
33
34impl SignalEvent {
35 pub fn new(signal_name: String, signal_number: i32) -> Self {
36 Self {
37 signal_name,
38 signal_number,
39 timestamp: Instant::now(),
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
45pub struct ColorScheme {
46 pub prompt: String,
48 pub error: String,
50 pub success: String,
52 pub builtin: String,
54 pub directory: String,
56}
57
58impl Default for ColorScheme {
59 fn default() -> Self {
60 Self {
61 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(), }
67 }
68}
69
70#[derive(Debug, Clone)]
71pub struct ShellState {
72 pub variables: HashMap<String, String>,
74 pub exported: HashSet<String>,
76 pub last_exit_code: i32,
78 pub shell_pid: u32,
80 pub script_name: String,
82 pub dir_stack: Vec<String>,
84 pub aliases: HashMap<String, String>,
86 pub colors_enabled: bool,
88 pub color_scheme: ColorScheme,
90 pub positional_params: Vec<String>,
92 pub functions: HashMap<String, Ast>,
94 pub local_vars: Vec<HashMap<String, String>>,
96 pub function_depth: usize,
98 pub max_recursion_depth: usize,
100 pub returning: bool,
102 pub return_value: Option<i32>,
104 pub capture_output: Option<Rc<RefCell<Vec<u8>>>>,
106 pub condensed_cwd: bool,
108 pub trap_handlers: Arc<Mutex<HashMap<String, String>>>,
110 pub exit_trap_executed: bool,
112 pub exit_requested: bool,
114 pub exit_code: i32,
116 #[allow(dead_code)]
119 pub pending_signals: bool,
120}
121
122impl ShellState {
123 pub fn new() -> Self {
124 let shell_pid = std::process::id();
125
126 let no_color = std::env::var("NO_COLOR").is_ok();
128
129 let rush_colors = std::env::var("RUSH_COLORS")
131 .map(|v| v.to_lowercase())
132 .unwrap_or_else(|_| "auto".to_string());
133
134 let colors_enabled = match rush_colors.as_str() {
135 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
136 "0" | "false" | "off" | "disable" => false,
137 "auto" => !no_color && std::io::stdout().is_terminal(),
138 _ => !no_color && std::io::stdout().is_terminal(),
139 };
140
141 let rush_condensed = std::env::var("RUSH_CONDENSED")
143 .map(|v| v.to_lowercase())
144 .unwrap_or_else(|_| "true".to_string());
145
146 let condensed_cwd = match rush_condensed.as_str() {
147 "1" | "true" | "on" | "enable" => true,
148 "0" | "false" | "off" | "disable" => false,
149 _ => true, };
151
152 Self {
153 variables: HashMap::new(),
154 exported: HashSet::new(),
155 last_exit_code: 0,
156 shell_pid,
157 script_name: "rush".to_string(),
158 dir_stack: Vec::new(),
159 aliases: HashMap::new(),
160 colors_enabled,
161 color_scheme: ColorScheme::default(),
162 positional_params: Vec::new(),
163 functions: HashMap::new(),
164 local_vars: Vec::new(),
165 function_depth: 0,
166 max_recursion_depth: 500, returning: false,
168 return_value: None,
169 capture_output: None,
170 condensed_cwd,
171 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
172 exit_trap_executed: false,
173 exit_requested: false,
174 exit_code: 0,
175 pending_signals: false,
176 }
177 }
178
179 pub fn get_var(&self, name: &str) -> Option<String> {
181 match name {
183 "?" => Some(self.last_exit_code.to_string()),
184 "$" => Some(self.shell_pid.to_string()),
185 "0" => Some(self.script_name.clone()),
186 "*" => {
187 if self.positional_params.is_empty() {
189 Some("".to_string())
190 } else {
191 Some(self.positional_params.join(" "))
192 }
193 }
194 "@" => {
195 if self.positional_params.is_empty() {
197 Some("".to_string())
198 } else {
199 Some(self.positional_params.join(" "))
200 }
201 }
202 "#" => Some(self.positional_params.len().to_string()),
203 _ => {
204 if let Ok(index) = name.parse::<usize>()
206 && index > 0
207 && index <= self.positional_params.len()
208 {
209 return Some(self.positional_params[index - 1].clone());
210 }
211
212 for scope in self.local_vars.iter().rev() {
215 if let Some(value) = scope.get(name) {
216 return Some(value.clone());
217 }
218 }
219
220 if let Some(value) = self.variables.get(name) {
222 Some(value.clone())
223 } else {
224 env::var(name).ok()
226 }
227 }
228 }
229 }
230
231 pub fn set_var(&mut self, name: &str, value: String) {
233 for scope in self.local_vars.iter_mut().rev() {
236 if scope.contains_key(name) {
237 scope.insert(name.to_string(), value);
238 return;
239 }
240 }
241
242 self.variables.insert(name.to_string(), value);
244 }
245
246 pub fn unset_var(&mut self, name: &str) {
248 self.variables.remove(name);
249 self.exported.remove(name);
250 }
251
252 pub fn export_var(&mut self, name: &str) {
254 if self.variables.contains_key(name) {
255 self.exported.insert(name.to_string());
256 }
257 }
258
259 pub fn set_exported_var(&mut self, name: &str, value: String) {
261 self.set_var(name, value);
262 self.export_var(name);
263 }
264
265 pub fn get_env_for_child(&self) -> HashMap<String, String> {
267 let mut child_env = HashMap::new();
268
269 for (key, value) in env::vars() {
271 child_env.insert(key, value);
272 }
273
274 for var_name in &self.exported {
276 if let Some(value) = self.variables.get(var_name) {
277 child_env.insert(var_name.clone(), value.clone());
278 }
279 }
280
281 child_env
282 }
283
284 pub fn set_last_exit_code(&mut self, code: i32) {
286 self.last_exit_code = code;
287 }
288
289 pub fn set_script_name(&mut self, name: &str) {
291 self.script_name = name.to_string();
292 }
293
294 pub fn get_condensed_cwd(&self) -> String {
296 match std::env::current_dir() {
297 Ok(path) => {
298 let path_str = path.to_string_lossy();
299 let components: Vec<&str> = path_str.split('/').collect();
300 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
301 return "/".to_string();
302 }
303 let mut result = String::new();
304 for (i, comp) in components.iter().enumerate() {
305 if comp.is_empty() {
306 continue; }
308 if i == components.len() - 1 {
309 result.push('/');
310 result.push_str(comp);
311 } else {
312 result.push('/');
313 if let Some(first) = comp.chars().next() {
314 result.push(first);
315 }
316 }
317 }
318 if result.is_empty() {
319 "/".to_string()
320 } else {
321 result
322 }
323 }
324 Err(_) => "/?".to_string(), }
326 }
327
328 pub fn get_full_cwd(&self) -> String {
330 match std::env::current_dir() {
331 Ok(path) => path.to_string_lossy().to_string(),
332 Err(_) => "/?".to_string(), }
334 }
335
336 pub fn get_user_hostname(&self) -> String {
338 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
339
340 if let Ok(hostname) = env::var("HOSTNAME")
342 && !hostname.trim().is_empty() {
343 return format!("{}@{}", user, hostname);
344 }
345
346 let hostname = match std::process::Command::new("hostname")
348 .output() {
349 Ok(output) if output.status.success() => {
350 String::from_utf8_lossy(&output.stdout)
351 .trim()
352 .to_string()
353 }
354 _ => "hostname".to_string(), };
356
357 if hostname != "hostname" {
359 unsafe {
360 std::env::set_var("HOSTNAME", &hostname);
361 }
362 }
363
364 format!("{}@{}", user, hostname)
365 }
366
367 pub fn get_prompt(&self) -> String {
369 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
370 let prompt_char = if user == "root" { "#" } else { "$" };
371 let cwd = if self.condensed_cwd {
372 self.get_condensed_cwd()
373 } else {
374 self.get_full_cwd()
375 };
376 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
377 }
378
379 pub fn set_alias(&mut self, name: &str, value: String) {
381 self.aliases.insert(name.to_string(), value);
382 }
383
384 pub fn get_alias(&self, name: &str) -> Option<&String> {
386 self.aliases.get(name)
387 }
388
389 pub fn remove_alias(&mut self, name: &str) {
391 self.aliases.remove(name);
392 }
393
394 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
396 &self.aliases
397 }
398
399 pub fn set_positional_params(&mut self, params: Vec<String>) {
401 self.positional_params = params;
402 }
403
404 #[allow(dead_code)]
406 pub fn get_positional_params(&self) -> &[String] {
407 &self.positional_params
408 }
409
410 pub fn shift_positional_params(&mut self, count: usize) {
412 if count > 0 {
413 for _ in 0..count {
414 if !self.positional_params.is_empty() {
415 self.positional_params.remove(0);
416 }
417 }
418 }
419 }
420
421 #[allow(dead_code)]
423 pub fn push_positional_param(&mut self, param: String) {
424 self.positional_params.push(param);
425 }
426
427 pub fn define_function(&mut self, name: String, body: Ast) {
429 self.functions.insert(name, body);
430 }
431
432 pub fn get_function(&self, name: &str) -> Option<&Ast> {
434 self.functions.get(name)
435 }
436
437 #[allow(dead_code)]
439 pub fn remove_function(&mut self, name: &str) {
440 self.functions.remove(name);
441 }
442
443 #[allow(dead_code)]
445 pub fn get_function_names(&self) -> Vec<&String> {
446 self.functions.keys().collect()
447 }
448
449 pub fn push_local_scope(&mut self) {
451 self.local_vars.push(HashMap::new());
452 }
453
454 pub fn pop_local_scope(&mut self) {
456 if !self.local_vars.is_empty() {
457 self.local_vars.pop();
458 }
459 }
460
461 pub fn set_local_var(&mut self, name: &str, value: String) {
463 if let Some(current_scope) = self.local_vars.last_mut() {
464 current_scope.insert(name.to_string(), value);
465 } else {
466 self.set_var(name, value);
468 }
469 }
470
471 pub fn enter_function(&mut self) {
473 self.function_depth += 1;
474 if self.function_depth > self.local_vars.len() {
475 self.push_local_scope();
476 }
477 }
478
479 pub fn exit_function(&mut self) {
481 if self.function_depth > 0 {
482 self.function_depth -= 1;
483 if self.function_depth == self.local_vars.len() - 1 {
484 self.pop_local_scope();
485 }
486 }
487 }
488
489 pub fn set_return(&mut self, value: i32) {
491 self.returning = true;
492 self.return_value = Some(value);
493 }
494
495 pub fn clear_return(&mut self) {
497 self.returning = false;
498 self.return_value = None;
499 }
500
501 pub fn is_returning(&self) -> bool {
503 self.returning
504 }
505
506 pub fn get_return_value(&self) -> Option<i32> {
508 self.return_value
509 }
510
511 pub fn set_trap(&mut self, signal: &str, command: String) {
513 if let Ok(mut handlers) = self.trap_handlers.lock() {
514 handlers.insert(signal.to_uppercase(), command);
515 }
516 }
517
518 pub fn get_trap(&self, signal: &str) -> Option<String> {
520 if let Ok(handlers) = self.trap_handlers.lock() {
521 handlers.get(&signal.to_uppercase()).cloned()
522 } else {
523 None
524 }
525 }
526
527 pub fn remove_trap(&mut self, signal: &str) {
529 if let Ok(mut handlers) = self.trap_handlers.lock() {
530 handlers.remove(&signal.to_uppercase());
531 }
532 }
533
534 pub fn get_all_traps(&self) -> HashMap<String, String> {
536 if let Ok(handlers) = self.trap_handlers.lock() {
537 handlers.clone()
538 } else {
539 HashMap::new()
540 }
541 }
542
543 #[allow(dead_code)]
545 pub fn clear_traps(&mut self) {
546 if let Ok(mut handlers) = self.trap_handlers.lock() {
547 handlers.clear();
548 }
549 }
550}
551
552pub fn enqueue_signal(signal_name: &str, signal_number: i32) {
555 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
556 if queue.len() >= MAX_SIGNAL_QUEUE_SIZE {
558 queue.pop_front();
559 eprintln!("Warning: Signal queue overflow, dropping oldest signal");
560 }
561
562 queue.push_back(SignalEvent::new(signal_name.to_string(), signal_number));
563 }
564}
565
566pub fn process_pending_signals(shell_state: &mut ShellState) {
569 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
571 while let Some(signal_event) = queue.pop_front() {
573 if let Some(trap_cmd) = shell_state.get_trap(&signal_event.signal_name)
575 && !trap_cmd.is_empty() {
576 crate::executor::execute_trap_handler(&trap_cmd, shell_state);
579 }
580 }
581 }
582}
583
584impl Default for ShellState {
585 fn default() -> Self {
586 Self::new()
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 #[test]
595 fn test_shell_state_basic() {
596 let mut state = ShellState::new();
597 state.set_var("TEST_VAR", "test_value".to_string());
598 assert_eq!(state.get_var("TEST_VAR"), Some("test_value".to_string()));
599 }
600
601 #[test]
602 fn test_special_variables() {
603 let mut state = ShellState::new();
604 state.set_last_exit_code(42);
605 state.set_script_name("test_script");
606
607 assert_eq!(state.get_var("?"), Some("42".to_string()));
608 assert_eq!(state.get_var("$"), Some(state.shell_pid.to_string()));
609 assert_eq!(state.get_var("0"), Some("test_script".to_string()));
610 }
611
612 #[test]
613 fn test_export_variable() {
614 let mut state = ShellState::new();
615 state.set_var("EXPORT_VAR", "export_value".to_string());
616 state.export_var("EXPORT_VAR");
617
618 let child_env = state.get_env_for_child();
619 assert_eq!(
620 child_env.get("EXPORT_VAR"),
621 Some(&"export_value".to_string())
622 );
623 }
624
625 #[test]
626 fn test_unset_variable() {
627 let mut state = ShellState::new();
628 state.set_var("UNSET_VAR", "value".to_string());
629 state.export_var("UNSET_VAR");
630
631 assert!(state.variables.contains_key("UNSET_VAR"));
632 assert!(state.exported.contains("UNSET_VAR"));
633
634 state.unset_var("UNSET_VAR");
635
636 assert!(!state.variables.contains_key("UNSET_VAR"));
637 assert!(!state.exported.contains("UNSET_VAR"));
638 }
639
640 #[test]
641 fn test_get_user_hostname() {
642 let state = ShellState::new();
643 let user_hostname = state.get_user_hostname();
644 assert!(user_hostname.contains('@'));
646 }
647
648 #[test]
649 fn test_get_prompt() {
650 let state = ShellState::new();
651 let prompt = state.get_prompt();
652 assert!(prompt.ends_with(" $ "));
654 assert!(prompt.contains('@'));
655 }
656
657 #[test]
658 fn test_positional_parameters() {
659 let mut state = ShellState::new();
660 state.set_positional_params(vec![
661 "arg1".to_string(),
662 "arg2".to_string(),
663 "arg3".to_string(),
664 ]);
665
666 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
667 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
668 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
669 assert_eq!(state.get_var("4"), None);
670 assert_eq!(state.get_var("#"), Some("3".to_string()));
671 assert_eq!(state.get_var("*"), Some("arg1 arg2 arg3".to_string()));
672 assert_eq!(state.get_var("@"), Some("arg1 arg2 arg3".to_string()));
673 }
674
675 #[test]
676 fn test_positional_parameters_empty() {
677 let mut state = ShellState::new();
678 state.set_positional_params(vec![]);
679
680 assert_eq!(state.get_var("1"), None);
681 assert_eq!(state.get_var("#"), Some("0".to_string()));
682 assert_eq!(state.get_var("*"), Some("".to_string()));
683 assert_eq!(state.get_var("@"), Some("".to_string()));
684 }
685
686 #[test]
687 fn test_shift_positional_params() {
688 let mut state = ShellState::new();
689 state.set_positional_params(vec![
690 "arg1".to_string(),
691 "arg2".to_string(),
692 "arg3".to_string(),
693 ]);
694
695 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
696 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
697 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
698
699 state.shift_positional_params(1);
700
701 assert_eq!(state.get_var("1"), Some("arg2".to_string()));
702 assert_eq!(state.get_var("2"), Some("arg3".to_string()));
703 assert_eq!(state.get_var("3"), None);
704 assert_eq!(state.get_var("#"), Some("2".to_string()));
705
706 state.shift_positional_params(2);
707
708 assert_eq!(state.get_var("1"), None);
709 assert_eq!(state.get_var("#"), Some("0".to_string()));
710 }
711
712 #[test]
713 fn test_push_positional_param() {
714 let mut state = ShellState::new();
715 state.set_positional_params(vec!["arg1".to_string()]);
716
717 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
718 assert_eq!(state.get_var("#"), Some("1".to_string()));
719
720 state.push_positional_param("arg2".to_string());
721
722 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
723 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
724 assert_eq!(state.get_var("#"), Some("2".to_string()));
725 }
726
727 #[test]
728 fn test_local_variable_scoping() {
729 let mut state = ShellState::new();
730
731 state.set_var("global_var", "global_value".to_string());
733 assert_eq!(
734 state.get_var("global_var"),
735 Some("global_value".to_string())
736 );
737
738 state.push_local_scope();
740
741 state.set_local_var("global_var", "local_value".to_string());
743 assert_eq!(state.get_var("global_var"), Some("local_value".to_string()));
744
745 state.set_local_var("local_var", "local_only".to_string());
747 assert_eq!(state.get_var("local_var"), Some("local_only".to_string()));
748
749 state.pop_local_scope();
751
752 assert_eq!(
754 state.get_var("global_var"),
755 Some("global_value".to_string())
756 );
757 assert_eq!(state.get_var("local_var"), None);
758 }
759
760 #[test]
761 fn test_nested_local_scopes() {
762 let mut state = ShellState::new();
763
764 state.set_var("test_var", "global".to_string());
766
767 state.push_local_scope();
769 state.set_local_var("test_var", "level1".to_string());
770 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
771
772 state.push_local_scope();
774 state.set_local_var("test_var", "level2".to_string());
775 assert_eq!(state.get_var("test_var"), Some("level2".to_string()));
776
777 state.pop_local_scope();
779 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
780
781 state.pop_local_scope();
783 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
784 }
785
786 #[test]
787 fn test_variable_set_in_local_scope() {
788 let mut state = ShellState::new();
789
790 state.set_var("test_var", "global".to_string());
792 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
793
794 state.push_local_scope();
796 state.set_local_var("test_var", "local".to_string());
797 assert_eq!(state.get_var("test_var"), Some("local".to_string()));
798
799 state.pop_local_scope();
801 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
802 }
803
804 #[test]
805 fn test_condensed_cwd_environment_variable() {
806 let state = ShellState::new();
808 assert!(state.condensed_cwd);
809
810 unsafe {
812 std::env::set_var("RUSH_CONDENSED", "true");
813 }
814 let state = ShellState::new();
815 assert!(state.condensed_cwd);
816
817 unsafe {
819 std::env::set_var("RUSH_CONDENSED", "false");
820 }
821 let state = ShellState::new();
822 assert!(!state.condensed_cwd);
823
824 unsafe {
826 std::env::remove_var("RUSH_CONDENSED");
827 }
828 }
829
830 #[test]
831 fn test_get_full_cwd() {
832 let state = ShellState::new();
833 let full_cwd = state.get_full_cwd();
834 assert!(!full_cwd.is_empty());
835 assert!(full_cwd.contains('/') || full_cwd.contains('\\'));
837 }
838
839 #[test]
840 fn test_prompt_with_condensed_setting() {
841 let mut state = ShellState::new();
842
843 assert!(state.condensed_cwd);
845 let prompt_condensed = state.get_prompt();
846 assert!(prompt_condensed.contains('@'));
847
848 state.condensed_cwd = false;
850 let prompt_full = state.get_prompt();
851 assert!(prompt_full.contains('@'));
852
853 assert!(prompt_condensed.ends_with("$ ") || prompt_condensed.ends_with("# "));
855 assert!(prompt_full.ends_with("$ ") || prompt_full.ends_with("# "));
856 }
857}