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 pub pending_heredoc_content: Option<String>,
122 pub collecting_heredoc: Option<(String, String, String)>, }
125
126impl ShellState {
127 pub fn new() -> Self {
128 let shell_pid = std::process::id();
129
130 let no_color = env::var("NO_COLOR").is_ok();
132
133 let rush_colors = env::var("RUSH_COLORS")
135 .map(|v| v.to_lowercase())
136 .unwrap_or_else(|_| "auto".to_string());
137
138 let colors_enabled = match rush_colors.as_str() {
139 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
140 "0" | "false" | "off" | "disable" => false,
141 "auto" => !no_color && std::io::stdout().is_terminal(),
142 _ => !no_color && std::io::stdout().is_terminal(),
143 };
144
145 let rush_condensed = env::var("RUSH_CONDENSED")
147 .map(|v| v.to_lowercase())
148 .unwrap_or_else(|_| "true".to_string());
149
150 let condensed_cwd = match rush_condensed.as_str() {
151 "1" | "true" | "on" | "enable" => true,
152 "0" | "false" | "off" | "disable" => false,
153 _ => true, };
155
156 Self {
157 variables: HashMap::new(),
158 exported: HashSet::new(),
159 last_exit_code: 0,
160 shell_pid,
161 script_name: "rush".to_string(),
162 dir_stack: Vec::new(),
163 aliases: HashMap::new(),
164 colors_enabled,
165 color_scheme: ColorScheme::default(),
166 positional_params: Vec::new(),
167 functions: HashMap::new(),
168 local_vars: Vec::new(),
169 function_depth: 0,
170 max_recursion_depth: 500, returning: false,
172 return_value: None,
173 capture_output: None,
174 condensed_cwd,
175 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
176 exit_trap_executed: false,
177 exit_requested: false,
178 exit_code: 0,
179 pending_signals: false,
180 pending_heredoc_content: None,
181 collecting_heredoc: None,
182 }
183 }
184
185 pub fn get_var(&self, name: &str) -> Option<String> {
187 match name {
189 "?" => Some(self.last_exit_code.to_string()),
190 "$" => Some(self.shell_pid.to_string()),
191 "0" => Some(self.script_name.clone()),
192 "*" => {
193 if self.positional_params.is_empty() {
195 Some("".to_string())
196 } else {
197 Some(self.positional_params.join(" "))
198 }
199 }
200 "@" => {
201 if self.positional_params.is_empty() {
203 Some("".to_string())
204 } else {
205 Some(self.positional_params.join(" "))
206 }
207 }
208 "#" => Some(self.positional_params.len().to_string()),
209 _ => {
210 if let Ok(index) = name.parse::<usize>()
212 && index > 0
213 && index <= self.positional_params.len()
214 {
215 return Some(self.positional_params[index - 1].clone());
216 }
217
218 for scope in self.local_vars.iter().rev() {
221 if let Some(value) = scope.get(name) {
222 return Some(value.clone());
223 }
224 }
225
226 if let Some(value) = self.variables.get(name) {
228 Some(value.clone())
229 } else {
230 env::var(name).ok()
232 }
233 }
234 }
235 }
236
237 pub fn set_var(&mut self, name: &str, value: String) {
239 for scope in self.local_vars.iter_mut().rev() {
242 if scope.contains_key(name) {
243 scope.insert(name.to_string(), value);
244 return;
245 }
246 }
247
248 self.variables.insert(name.to_string(), value);
250 }
251
252 pub fn unset_var(&mut self, name: &str) {
254 self.variables.remove(name);
255 self.exported.remove(name);
256 }
257
258 pub fn export_var(&mut self, name: &str) {
260 if self.variables.contains_key(name) {
261 self.exported.insert(name.to_string());
262 }
263 }
264
265 pub fn set_exported_var(&mut self, name: &str, value: String) {
267 self.set_var(name, value);
268 self.export_var(name);
269 }
270
271 pub fn get_env_for_child(&self) -> HashMap<String, String> {
273 let mut child_env = HashMap::new();
274
275 for (key, value) in env::vars() {
277 child_env.insert(key, value);
278 }
279
280 for var_name in &self.exported {
282 if let Some(value) = self.variables.get(var_name) {
283 child_env.insert(var_name.clone(), value.clone());
284 }
285 }
286
287 child_env
288 }
289
290 pub fn set_last_exit_code(&mut self, code: i32) {
292 self.last_exit_code = code;
293 }
294
295 pub fn set_script_name(&mut self, name: &str) {
297 self.script_name = name.to_string();
298 }
299
300 pub fn get_condensed_cwd(&self) -> String {
302 match env::current_dir() {
303 Ok(path) => {
304 let path_str = path.to_string_lossy();
305 let components: Vec<&str> = path_str.split('/').collect();
306 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
307 return "/".to_string();
308 }
309 let mut result = String::new();
310 for (i, comp) in components.iter().enumerate() {
311 if comp.is_empty() {
312 continue; }
314 if i == components.len() - 1 {
315 result.push('/');
316 result.push_str(comp);
317 } else {
318 result.push('/');
319 if let Some(first) = comp.chars().next() {
320 result.push(first);
321 }
322 }
323 }
324 if result.is_empty() {
325 "/".to_string()
326 } else {
327 result
328 }
329 }
330 Err(_) => "/?".to_string(), }
332 }
333
334 pub fn get_full_cwd(&self) -> String {
336 match env::current_dir() {
337 Ok(path) => path.to_string_lossy().to_string(),
338 Err(_) => "/?".to_string(), }
340 }
341
342 pub fn get_user_hostname(&self) -> String {
344 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
345
346 if let Ok(hostname) = env::var("HOSTNAME")
348 && !hostname.trim().is_empty()
349 {
350 return format!("{}@{}", user, hostname);
351 }
352
353 let hostname = match std::process::Command::new("hostname").output() {
355 Ok(output) if output.status.success() => {
356 String::from_utf8_lossy(&output.stdout).trim().to_string()
357 }
358 _ => "hostname".to_string(), };
360
361 if hostname != "hostname" {
363 unsafe {
364 env::set_var("HOSTNAME", &hostname);
365 }
366 }
367
368 format!("{}@{}", user, hostname)
369 }
370
371 pub fn get_prompt(&self) -> String {
373 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
374 let prompt_char = if user == "root" { "#" } else { "$" };
375 let cwd = if self.condensed_cwd {
376 self.get_condensed_cwd()
377 } else {
378 self.get_full_cwd()
379 };
380 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
381 }
382
383 pub fn set_alias(&mut self, name: &str, value: String) {
385 self.aliases.insert(name.to_string(), value);
386 }
387
388 pub fn get_alias(&self, name: &str) -> Option<&String> {
390 self.aliases.get(name)
391 }
392
393 pub fn remove_alias(&mut self, name: &str) {
395 self.aliases.remove(name);
396 }
397
398 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
400 &self.aliases
401 }
402
403 pub fn set_positional_params(&mut self, params: Vec<String>) {
405 self.positional_params = params;
406 }
407
408 #[allow(dead_code)]
410 pub fn get_positional_params(&self) -> &[String] {
411 &self.positional_params
412 }
413
414 pub fn shift_positional_params(&mut self, count: usize) {
416 if count > 0 {
417 for _ in 0..count {
418 if !self.positional_params.is_empty() {
419 self.positional_params.remove(0);
420 }
421 }
422 }
423 }
424
425 #[allow(dead_code)]
427 pub fn push_positional_param(&mut self, param: String) {
428 self.positional_params.push(param);
429 }
430
431 pub fn define_function(&mut self, name: String, body: Ast) {
433 self.functions.insert(name, body);
434 }
435
436 pub fn get_function(&self, name: &str) -> Option<&Ast> {
438 self.functions.get(name)
439 }
440
441 #[allow(dead_code)]
443 pub fn remove_function(&mut self, name: &str) {
444 self.functions.remove(name);
445 }
446
447 #[allow(dead_code)]
449 pub fn get_function_names(&self) -> Vec<&String> {
450 self.functions.keys().collect()
451 }
452
453 pub fn push_local_scope(&mut self) {
455 self.local_vars.push(HashMap::new());
456 }
457
458 pub fn pop_local_scope(&mut self) {
460 if !self.local_vars.is_empty() {
461 self.local_vars.pop();
462 }
463 }
464
465 pub fn set_local_var(&mut self, name: &str, value: String) {
467 if let Some(current_scope) = self.local_vars.last_mut() {
468 current_scope.insert(name.to_string(), value);
469 } else {
470 self.set_var(name, value);
472 }
473 }
474
475 pub fn enter_function(&mut self) {
477 self.function_depth += 1;
478 if self.function_depth > self.local_vars.len() {
479 self.push_local_scope();
480 }
481 }
482
483 pub fn exit_function(&mut self) {
485 if self.function_depth > 0 {
486 self.function_depth -= 1;
487 if self.function_depth == self.local_vars.len() - 1 {
488 self.pop_local_scope();
489 }
490 }
491 }
492
493 pub fn set_return(&mut self, value: i32) {
495 self.returning = true;
496 self.return_value = Some(value);
497 }
498
499 pub fn clear_return(&mut self) {
501 self.returning = false;
502 self.return_value = None;
503 }
504
505 pub fn is_returning(&self) -> bool {
507 self.returning
508 }
509
510 pub fn get_return_value(&self) -> Option<i32> {
512 self.return_value
513 }
514
515 pub fn set_trap(&mut self, signal: &str, command: String) {
517 if let Ok(mut handlers) = self.trap_handlers.lock() {
518 handlers.insert(signal.to_uppercase(), command);
519 }
520 }
521
522 pub fn get_trap(&self, signal: &str) -> Option<String> {
524 if let Ok(handlers) = self.trap_handlers.lock() {
525 handlers.get(&signal.to_uppercase()).cloned()
526 } else {
527 None
528 }
529 }
530
531 pub fn remove_trap(&mut self, signal: &str) {
533 if let Ok(mut handlers) = self.trap_handlers.lock() {
534 handlers.remove(&signal.to_uppercase());
535 }
536 }
537
538 pub fn get_all_traps(&self) -> HashMap<String, String> {
540 if let Ok(handlers) = self.trap_handlers.lock() {
541 handlers.clone()
542 } else {
543 HashMap::new()
544 }
545 }
546
547 #[allow(dead_code)]
549 pub fn clear_traps(&mut self) {
550 if let Ok(mut handlers) = self.trap_handlers.lock() {
551 handlers.clear();
552 }
553 }
554}
555
556pub fn enqueue_signal(signal_name: &str, signal_number: i32) {
559 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
560 if queue.len() >= MAX_SIGNAL_QUEUE_SIZE {
562 queue.pop_front();
563 eprintln!("Warning: Signal queue overflow, dropping oldest signal");
564 }
565
566 queue.push_back(SignalEvent::new(signal_name.to_string(), signal_number));
567 }
568}
569
570pub fn process_pending_signals(shell_state: &mut ShellState) {
573 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
575 while let Some(signal_event) = queue.pop_front() {
577 if let Some(trap_cmd) = shell_state.get_trap(&signal_event.signal_name)
579 && !trap_cmd.is_empty()
580 {
581 crate::executor::execute_trap_handler(&trap_cmd, shell_state);
584 }
585 }
586 }
587}
588
589impl Default for ShellState {
590 fn default() -> Self {
591 Self::new()
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
600 fn test_shell_state_basic() {
601 let mut state = ShellState::new();
602 state.set_var("TEST_VAR", "test_value".to_string());
603 assert_eq!(state.get_var("TEST_VAR"), Some("test_value".to_string()));
604 }
605
606 #[test]
607 fn test_special_variables() {
608 let mut state = ShellState::new();
609 state.set_last_exit_code(42);
610 state.set_script_name("test_script");
611
612 assert_eq!(state.get_var("?"), Some("42".to_string()));
613 assert_eq!(state.get_var("$"), Some(state.shell_pid.to_string()));
614 assert_eq!(state.get_var("0"), Some("test_script".to_string()));
615 }
616
617 #[test]
618 fn test_export_variable() {
619 let mut state = ShellState::new();
620 state.set_var("EXPORT_VAR", "export_value".to_string());
621 state.export_var("EXPORT_VAR");
622
623 let child_env = state.get_env_for_child();
624 assert_eq!(
625 child_env.get("EXPORT_VAR"),
626 Some(&"export_value".to_string())
627 );
628 }
629
630 #[test]
631 fn test_unset_variable() {
632 let mut state = ShellState::new();
633 state.set_var("UNSET_VAR", "value".to_string());
634 state.export_var("UNSET_VAR");
635
636 assert!(state.variables.contains_key("UNSET_VAR"));
637 assert!(state.exported.contains("UNSET_VAR"));
638
639 state.unset_var("UNSET_VAR");
640
641 assert!(!state.variables.contains_key("UNSET_VAR"));
642 assert!(!state.exported.contains("UNSET_VAR"));
643 }
644
645 #[test]
646 fn test_get_user_hostname() {
647 let state = ShellState::new();
648 let user_hostname = state.get_user_hostname();
649 assert!(user_hostname.contains('@'));
651 }
652
653 #[test]
654 fn test_get_prompt() {
655 let state = ShellState::new();
656 let prompt = state.get_prompt();
657 assert!(prompt.ends_with(" $ "));
659 assert!(prompt.contains('@'));
660 }
661
662 #[test]
663 fn test_positional_parameters() {
664 let mut state = ShellState::new();
665 state.set_positional_params(vec![
666 "arg1".to_string(),
667 "arg2".to_string(),
668 "arg3".to_string(),
669 ]);
670
671 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
672 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
673 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
674 assert_eq!(state.get_var("4"), None);
675 assert_eq!(state.get_var("#"), Some("3".to_string()));
676 assert_eq!(state.get_var("*"), Some("arg1 arg2 arg3".to_string()));
677 assert_eq!(state.get_var("@"), Some("arg1 arg2 arg3".to_string()));
678 }
679
680 #[test]
681 fn test_positional_parameters_empty() {
682 let mut state = ShellState::new();
683 state.set_positional_params(vec![]);
684
685 assert_eq!(state.get_var("1"), None);
686 assert_eq!(state.get_var("#"), Some("0".to_string()));
687 assert_eq!(state.get_var("*"), Some("".to_string()));
688 assert_eq!(state.get_var("@"), Some("".to_string()));
689 }
690
691 #[test]
692 fn test_shift_positional_params() {
693 let mut state = ShellState::new();
694 state.set_positional_params(vec![
695 "arg1".to_string(),
696 "arg2".to_string(),
697 "arg3".to_string(),
698 ]);
699
700 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
701 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
702 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
703
704 state.shift_positional_params(1);
705
706 assert_eq!(state.get_var("1"), Some("arg2".to_string()));
707 assert_eq!(state.get_var("2"), Some("arg3".to_string()));
708 assert_eq!(state.get_var("3"), None);
709 assert_eq!(state.get_var("#"), Some("2".to_string()));
710
711 state.shift_positional_params(2);
712
713 assert_eq!(state.get_var("1"), None);
714 assert_eq!(state.get_var("#"), Some("0".to_string()));
715 }
716
717 #[test]
718 fn test_push_positional_param() {
719 let mut state = ShellState::new();
720 state.set_positional_params(vec!["arg1".to_string()]);
721
722 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
723 assert_eq!(state.get_var("#"), Some("1".to_string()));
724
725 state.push_positional_param("arg2".to_string());
726
727 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
728 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
729 assert_eq!(state.get_var("#"), Some("2".to_string()));
730 }
731
732 #[test]
733 fn test_local_variable_scoping() {
734 let mut state = ShellState::new();
735
736 state.set_var("global_var", "global_value".to_string());
738 assert_eq!(
739 state.get_var("global_var"),
740 Some("global_value".to_string())
741 );
742
743 state.push_local_scope();
745
746 state.set_local_var("global_var", "local_value".to_string());
748 assert_eq!(state.get_var("global_var"), Some("local_value".to_string()));
749
750 state.set_local_var("local_var", "local_only".to_string());
752 assert_eq!(state.get_var("local_var"), Some("local_only".to_string()));
753
754 state.pop_local_scope();
756
757 assert_eq!(
759 state.get_var("global_var"),
760 Some("global_value".to_string())
761 );
762 assert_eq!(state.get_var("local_var"), None);
763 }
764
765 #[test]
766 fn test_nested_local_scopes() {
767 let mut state = ShellState::new();
768
769 state.set_var("test_var", "global".to_string());
771
772 state.push_local_scope();
774 state.set_local_var("test_var", "level1".to_string());
775 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
776
777 state.push_local_scope();
779 state.set_local_var("test_var", "level2".to_string());
780 assert_eq!(state.get_var("test_var"), Some("level2".to_string()));
781
782 state.pop_local_scope();
784 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
785
786 state.pop_local_scope();
788 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
789 }
790
791 #[test]
792 fn test_variable_set_in_local_scope() {
793 let mut state = ShellState::new();
794
795 state.set_var("test_var", "global".to_string());
797 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
798
799 state.push_local_scope();
801 state.set_local_var("test_var", "local".to_string());
802 assert_eq!(state.get_var("test_var"), Some("local".to_string()));
803
804 state.pop_local_scope();
806 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
807 }
808
809 #[test]
810 fn test_condensed_cwd_environment_variable() {
811 let state = ShellState::new();
813 assert!(state.condensed_cwd);
814
815 unsafe {
817 env::set_var("RUSH_CONDENSED", "true");
818 }
819 let state = ShellState::new();
820 assert!(state.condensed_cwd);
821
822 unsafe {
824 env::set_var("RUSH_CONDENSED", "false");
825 }
826 let state = ShellState::new();
827 assert!(!state.condensed_cwd);
828
829 unsafe {
831 env::remove_var("RUSH_CONDENSED");
832 }
833 }
834
835 #[test]
836 fn test_get_full_cwd() {
837 let state = ShellState::new();
838 let full_cwd = state.get_full_cwd();
839 assert!(!full_cwd.is_empty());
840 assert!(full_cwd.contains('/') || full_cwd.contains('\\'));
842 }
843
844 #[test]
845 fn test_prompt_with_condensed_setting() {
846 let mut state = ShellState::new();
847
848 assert!(state.condensed_cwd);
850 let prompt_condensed = state.get_prompt();
851 assert!(prompt_condensed.contains('@'));
852
853 state.condensed_cwd = false;
855 let prompt_full = state.get_prompt();
856 assert!(prompt_full.contains('@'));
857
858 assert!(prompt_condensed.ends_with("$ ") || prompt_condensed.ends_with("# "));
860 assert!(prompt_full.ends_with("$ ") || prompt_full.ends_with("# "));
861 }
862}