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