1pub mod fd_table;
3
4pub mod options;
6
7pub mod signals;
9
10pub use fd_table::FileDescriptorTable;
12pub use options::ShellOptions;
13pub use signals::{enqueue_signal, process_pending_signals};
14
15use super::parser::Ast;
16use std::cell::RefCell;
17use std::collections::{HashMap, HashSet};
18use std::env;
19use std::io::IsTerminal;
20use std::os::unix::io::RawFd;
21use std::rc::Rc;
22use std::sync::{Arc, Mutex};
23
24#[derive(Debug, Clone)]
25pub struct ColorScheme {
26 pub prompt: String,
28 pub error: String,
30 pub success: String,
32 pub builtin: String,
34 pub directory: String,
36}
37
38impl Default for ColorScheme {
39 fn default() -> Self {
40 Self {
41 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(), }
47 }
48}
49
50#[derive(Debug, Clone)]
51pub struct ShellState {
52 pub variables: HashMap<String, String>,
54 pub exported: HashSet<String>,
56 pub last_exit_code: i32,
58 pub shell_pid: u32,
60 pub script_name: String,
62 pub dir_stack: Vec<String>,
64 pub aliases: HashMap<String, String>,
66 pub colors_enabled: bool,
68 pub color_scheme: ColorScheme,
70 pub positional_params: Vec<String>,
72 pub functions: HashMap<String, Ast>,
74 pub local_vars: Vec<HashMap<String, String>>,
76 pub function_depth: usize,
78 pub max_recursion_depth: usize,
80 pub returning: bool,
82 pub return_value: Option<i32>,
84 pub loop_depth: usize,
86 pub breaking: bool,
88 pub break_level: usize,
90 pub continuing: bool,
92 pub continue_level: usize,
94 pub capture_output: Option<Rc<RefCell<Vec<u8>>>>,
96 pub condensed_cwd: bool,
98 pub trap_handlers: Arc<Mutex<HashMap<String, String>>>,
100 pub exit_trap_executed: bool,
102 pub exit_requested: bool,
104 pub exit_code: i32,
106 #[allow(dead_code)]
109 pub pending_signals: bool,
110 pub pending_heredoc_content: Option<String>,
112 pub collecting_heredoc: Option<(String, String, String)>, pub fd_table: Rc<RefCell<FileDescriptorTable>>,
116 pub subshell_depth: usize,
118 pub stdin_override: Option<RawFd>,
120 pub options: ShellOptions,
122 pub in_condition: bool,
124 pub in_logical_chain: bool,
126 pub in_negation: bool,
128 pub last_was_negation: bool,
130 pub current_line_number: usize,
132 pub line_number_stack: Vec<usize>,
134}
135
136impl ShellState {
137 pub fn new() -> Self {
153 let shell_pid = std::process::id();
154
155 let no_color = env::var("NO_COLOR").is_ok();
157
158 let rush_colors = env::var("RUSH_COLORS")
160 .map(|v| v.to_lowercase())
161 .unwrap_or_else(|_| "auto".to_string());
162
163 let colors_enabled = match rush_colors.as_str() {
164 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
165 "0" | "false" | "off" | "disable" => false,
166 "auto" => !no_color && std::io::stdout().is_terminal(),
167 _ => !no_color && std::io::stdout().is_terminal(),
168 };
169
170 let rush_condensed = env::var("RUSH_CONDENSED")
172 .map(|v| v.to_lowercase())
173 .unwrap_or_else(|_| "true".to_string());
174
175 let condensed_cwd = match rush_condensed.as_str() {
176 "1" | "true" | "on" | "enable" => true,
177 "0" | "false" | "off" | "disable" => false,
178 _ => true, };
180
181 Self {
182 variables: HashMap::new(),
183 exported: HashSet::new(),
184 last_exit_code: 0,
185 shell_pid,
186 script_name: "rush".to_string(),
187 dir_stack: Vec::new(),
188 aliases: HashMap::new(),
189 colors_enabled,
190 color_scheme: ColorScheme::default(),
191 positional_params: Vec::new(),
192 functions: HashMap::new(),
193 local_vars: Vec::new(),
194 function_depth: 0,
195 max_recursion_depth: 500, returning: false,
197 return_value: None,
198 loop_depth: 0,
199 breaking: false,
200 break_level: 0,
201 continuing: false,
202 continue_level: 0,
203 capture_output: None,
204 condensed_cwd,
205 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
206 exit_trap_executed: false,
207 exit_requested: false,
208 exit_code: 0,
209 pending_signals: false,
210 pending_heredoc_content: None,
211 collecting_heredoc: None,
212 fd_table: Rc::new(RefCell::new(FileDescriptorTable::new())),
213 subshell_depth: 0,
214 stdin_override: None,
215 options: ShellOptions::default(),
216 in_condition: false,
217 in_logical_chain: false,
218 in_negation: false,
219 last_was_negation: false,
220 current_line_number: 1,
221 line_number_stack: Vec::new(),
222 }
223 }
224
225 pub fn get_var(&self, name: &str) -> Option<String> {
227 match name {
229 "?" => Some(self.last_exit_code.to_string()),
230 "$" => Some(self.shell_pid.to_string()),
231 "0" => Some(self.script_name.clone()),
232 "LINENO" => Some(self.current_line_number.to_string()),
233 "*" => {
234 if self.positional_params.is_empty() {
236 Some("".to_string())
237 } else {
238 Some(self.positional_params.join(" "))
239 }
240 }
241 "@" => {
242 if self.positional_params.is_empty() {
244 Some("".to_string())
245 } else {
246 Some(self.positional_params.join(" "))
247 }
248 }
249 "#" => Some(self.positional_params.len().to_string()),
250 _ => {
251 if let Ok(index) = name.parse::<usize>()
253 && index > 0
254 && index <= self.positional_params.len()
255 {
256 return Some(self.positional_params[index - 1].clone());
257 }
258
259 for scope in self.local_vars.iter().rev() {
262 if let Some(value) = scope.get(name) {
263 return Some(value.clone());
264 }
265 }
266
267 if let Some(value) = self.variables.get(name) {
269 Some(value.clone())
270 } else {
271 env::var(name).ok()
273 }
274 }
275 }
276 }
277
278 pub fn set_var(&mut self, name: &str, value: String) {
280 for scope in self.local_vars.iter_mut().rev() {
283 if scope.contains_key(name) {
284 scope.insert(name.to_string(), value);
285 return;
286 }
287 }
288
289 self.variables.insert(name.to_string(), value);
291 }
292
293 pub fn unset_var(&mut self, name: &str) {
295 self.variables.remove(name);
296 self.exported.remove(name);
297 }
298
299 pub fn export_var(&mut self, name: &str) {
301 if self.variables.contains_key(name) {
302 self.exported.insert(name.to_string());
303 }
304 }
305
306 pub fn set_exported_var(&mut self, name: &str, value: String) {
308 self.set_var(name, value);
309 self.export_var(name);
310 }
311
312 pub fn get_env_for_child(&self) -> HashMap<String, String> {
314 let mut child_env = HashMap::new();
315
316 for (key, value) in env::vars() {
318 child_env.insert(key, value);
319 }
320
321 for var_name in &self.exported {
323 if let Some(value) = self.variables.get(var_name) {
324 child_env.insert(var_name.clone(), value.clone());
325 }
326 }
327
328 child_env
329 }
330
331 pub fn set_last_exit_code(&mut self, code: i32) {
333 self.last_exit_code = code;
334 }
335
336 pub fn set_script_name(&mut self, name: &str) {
338 self.script_name = name.to_string();
339 }
340
341 pub fn get_condensed_cwd(&self) -> String {
343 match env::current_dir() {
344 Ok(path) => {
345 let path_str = path.to_string_lossy();
346 let components: Vec<&str> = path_str.split('/').collect();
347 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
348 return "/".to_string();
349 }
350 let mut result = String::new();
351 for (i, comp) in components.iter().enumerate() {
352 if comp.is_empty() {
353 continue; }
355 if i == components.len() - 1 {
356 result.push('/');
357 result.push_str(comp);
358 } else {
359 result.push('/');
360 if let Some(first) = comp.chars().next() {
361 result.push(first);
362 }
363 }
364 }
365 if result.is_empty() {
366 "/".to_string()
367 } else {
368 result
369 }
370 }
371 Err(_) => "/?".to_string(), }
373 }
374
375 pub fn get_full_cwd(&self) -> String {
377 match env::current_dir() {
378 Ok(path) => path.to_string_lossy().to_string(),
379 Err(_) => "/?".to_string(), }
381 }
382
383 pub fn get_user_hostname(&self) -> String {
385 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
386
387 if let Ok(hostname) = env::var("HOSTNAME")
389 && !hostname.trim().is_empty()
390 {
391 return format!("{}@{}", user, hostname);
392 }
393
394 let hostname = match std::process::Command::new("hostname").output() {
396 Ok(output) if output.status.success() => {
397 String::from_utf8_lossy(&output.stdout).trim().to_string()
398 }
399 _ => "hostname".to_string(), };
401
402 if hostname != "hostname" {
404 unsafe {
405 env::set_var("HOSTNAME", &hostname);
406 }
407 }
408
409 format!("{}@{}", user, hostname)
410 }
411
412 pub fn get_prompt(&self) -> String {
414 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
415 let prompt_char = if user == "root" { "#" } else { "$" };
416 let cwd = if self.condensed_cwd {
417 self.get_condensed_cwd()
418 } else {
419 self.get_full_cwd()
420 };
421 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
422 }
423
424 pub fn set_alias(&mut self, name: &str, value: String) {
426 self.aliases.insert(name.to_string(), value);
427 }
428
429 pub fn get_alias(&self, name: &str) -> Option<&String> {
431 self.aliases.get(name)
432 }
433
434 pub fn remove_alias(&mut self, name: &str) {
436 self.aliases.remove(name);
437 }
438
439 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
441 &self.aliases
442 }
443
444 pub fn set_positional_params(&mut self, params: Vec<String>) {
446 self.positional_params = params;
447 }
448
449 #[allow(dead_code)]
451 pub fn get_positional_params(&self) -> &[String] {
452 &self.positional_params
453 }
454
455 pub fn shift_positional_params(&mut self, count: usize) {
457 if count > 0 {
458 for _ in 0..count {
459 if !self.positional_params.is_empty() {
460 self.positional_params.remove(0);
461 }
462 }
463 }
464 }
465
466 #[allow(dead_code)]
468 pub fn push_positional_param(&mut self, param: String) {
469 self.positional_params.push(param);
470 }
471
472 pub fn define_function(&mut self, name: String, body: Ast) {
474 self.functions.insert(name, body);
475 }
476
477 pub fn get_function(&self, name: &str) -> Option<&Ast> {
479 self.functions.get(name)
480 }
481
482 #[allow(dead_code)]
484 pub fn remove_function(&mut self, name: &str) {
485 self.functions.remove(name);
486 }
487
488 #[allow(dead_code)]
490 pub fn get_function_names(&self) -> Vec<&String> {
491 self.functions.keys().collect()
492 }
493
494 pub fn push_local_scope(&mut self) {
496 self.local_vars.push(HashMap::new());
497 }
498
499 pub fn pop_local_scope(&mut self) {
501 if !self.local_vars.is_empty() {
502 self.local_vars.pop();
503 }
504 }
505
506 pub fn set_local_var(&mut self, name: &str, value: String) {
508 if let Some(current_scope) = self.local_vars.last_mut() {
509 current_scope.insert(name.to_string(), value);
510 } else {
511 self.set_var(name, value);
513 }
514 }
515
516 pub fn enter_function(&mut self) {
518 self.function_depth += 1;
519 if self.function_depth > self.local_vars.len() {
520 self.push_local_scope();
521 }
522 }
523
524 pub fn exit_function(&mut self) {
526 if self.function_depth > 0 {
527 self.function_depth -= 1;
528 if self.function_depth == self.local_vars.len() - 1 {
529 self.pop_local_scope();
530 }
531 }
532 }
533
534 pub fn set_return(&mut self, value: i32) {
536 self.returning = true;
537 self.return_value = Some(value);
538 }
539
540 pub fn clear_return(&mut self) {
542 self.returning = false;
543 self.return_value = None;
544 }
545
546 pub fn is_returning(&self) -> bool {
548 self.returning
549 }
550
551 pub fn get_return_value(&self) -> Option<i32> {
553 self.return_value
554 }
555
556 pub fn enter_loop(&mut self) {
558 self.loop_depth += 1;
559 }
560
561 pub fn exit_loop(&mut self) {
563 if self.loop_depth > 0 {
564 self.loop_depth -= 1;
565 }
566 }
567
568 pub fn set_break(&mut self, level: usize) {
570 self.breaking = true;
571 self.break_level = level;
572 }
573
574 pub fn clear_break(&mut self) {
576 self.breaking = false;
577 self.break_level = 0;
578 }
579
580 pub fn is_breaking(&self) -> bool {
582 self.breaking
583 }
584
585 pub fn get_break_level(&self) -> usize {
587 self.break_level
588 }
589
590 pub fn decrement_break_level(&mut self) {
592 if self.break_level > 0 {
593 self.break_level -= 1;
594 }
595 if self.break_level == 0 {
596 self.breaking = false;
597 }
598 }
599
600 pub fn set_continue(&mut self, level: usize) {
602 self.continuing = true;
603 self.continue_level = level;
604 }
605
606 pub fn clear_continue(&mut self) {
608 self.continuing = false;
609 self.continue_level = 0;
610 }
611
612 pub fn is_continuing(&self) -> bool {
614 self.continuing
615 }
616
617 pub fn get_continue_level(&self) -> usize {
619 self.continue_level
620 }
621
622 pub fn decrement_continue_level(&mut self) {
624 if self.continue_level > 0 {
625 self.continue_level -= 1;
626 }
627 if self.continue_level == 0 {
628 self.continuing = false;
629 }
630 }
631
632 pub fn set_trap(&mut self, signal: &str, command: String) {
634 if let Ok(mut handlers) = self.trap_handlers.lock() {
635 handlers.insert(signal.to_uppercase(), command);
636 }
637 }
638
639 pub fn get_trap(&self, signal: &str) -> Option<String> {
641 if let Ok(handlers) = self.trap_handlers.lock() {
642 handlers.get(&signal.to_uppercase()).cloned()
643 } else {
644 None
645 }
646 }
647
648 pub fn remove_trap(&mut self, signal: &str) {
650 if let Ok(mut handlers) = self.trap_handlers.lock() {
651 handlers.remove(&signal.to_uppercase());
652 }
653 }
654
655 pub fn get_all_traps(&self) -> HashMap<String, String> {
657 if let Ok(handlers) = self.trap_handlers.lock() {
658 handlers.clone()
659 } else {
660 HashMap::new()
661 }
662 }
663
664 #[allow(dead_code)]
666 pub fn clear_traps(&mut self) {
667 if let Ok(mut handlers) = self.trap_handlers.lock() {
668 handlers.clear();
669 }
670 }
671}
672
673impl Default for ShellState {
674 fn default() -> Self {
675 Self::new()
676 }
677}
678
679#[cfg(test)]
680mod tests;