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}
131
132impl ShellState {
133 pub fn new() -> Self {
149 let shell_pid = std::process::id();
150
151 let no_color = env::var("NO_COLOR").is_ok();
153
154 let rush_colors = env::var("RUSH_COLORS")
156 .map(|v| v.to_lowercase())
157 .unwrap_or_else(|_| "auto".to_string());
158
159 let colors_enabled = match rush_colors.as_str() {
160 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
161 "0" | "false" | "off" | "disable" => false,
162 "auto" => !no_color && std::io::stdout().is_terminal(),
163 _ => !no_color && std::io::stdout().is_terminal(),
164 };
165
166 let rush_condensed = env::var("RUSH_CONDENSED")
168 .map(|v| v.to_lowercase())
169 .unwrap_or_else(|_| "true".to_string());
170
171 let condensed_cwd = match rush_condensed.as_str() {
172 "1" | "true" | "on" | "enable" => true,
173 "0" | "false" | "off" | "disable" => false,
174 _ => true, };
176
177 Self {
178 variables: HashMap::new(),
179 exported: HashSet::new(),
180 last_exit_code: 0,
181 shell_pid,
182 script_name: "rush".to_string(),
183 dir_stack: Vec::new(),
184 aliases: HashMap::new(),
185 colors_enabled,
186 color_scheme: ColorScheme::default(),
187 positional_params: Vec::new(),
188 functions: HashMap::new(),
189 local_vars: Vec::new(),
190 function_depth: 0,
191 max_recursion_depth: 500, returning: false,
193 return_value: None,
194 loop_depth: 0,
195 breaking: false,
196 break_level: 0,
197 continuing: false,
198 continue_level: 0,
199 capture_output: None,
200 condensed_cwd,
201 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
202 exit_trap_executed: false,
203 exit_requested: false,
204 exit_code: 0,
205 pending_signals: false,
206 pending_heredoc_content: None,
207 collecting_heredoc: None,
208 fd_table: Rc::new(RefCell::new(FileDescriptorTable::new())),
209 subshell_depth: 0,
210 stdin_override: None,
211 options: ShellOptions::default(),
212 in_condition: false,
213 in_logical_chain: false,
214 in_negation: false,
215 last_was_negation: false,
216 }
217 }
218
219 pub fn get_var(&self, name: &str) -> Option<String> {
221 match name {
223 "?" => Some(self.last_exit_code.to_string()),
224 "$" => Some(self.shell_pid.to_string()),
225 "0" => Some(self.script_name.clone()),
226 "*" => {
227 if self.positional_params.is_empty() {
229 Some("".to_string())
230 } else {
231 Some(self.positional_params.join(" "))
232 }
233 }
234 "@" => {
235 if self.positional_params.is_empty() {
237 Some("".to_string())
238 } else {
239 Some(self.positional_params.join(" "))
240 }
241 }
242 "#" => Some(self.positional_params.len().to_string()),
243 _ => {
244 if let Ok(index) = name.parse::<usize>()
246 && index > 0
247 && index <= self.positional_params.len()
248 {
249 return Some(self.positional_params[index - 1].clone());
250 }
251
252 for scope in self.local_vars.iter().rev() {
255 if let Some(value) = scope.get(name) {
256 return Some(value.clone());
257 }
258 }
259
260 if let Some(value) = self.variables.get(name) {
262 Some(value.clone())
263 } else {
264 env::var(name).ok()
266 }
267 }
268 }
269 }
270
271 pub fn set_var(&mut self, name: &str, value: String) {
273 for scope in self.local_vars.iter_mut().rev() {
276 if scope.contains_key(name) {
277 scope.insert(name.to_string(), value);
278 return;
279 }
280 }
281
282 self.variables.insert(name.to_string(), value);
284 }
285
286 pub fn unset_var(&mut self, name: &str) {
288 self.variables.remove(name);
289 self.exported.remove(name);
290 }
291
292 pub fn export_var(&mut self, name: &str) {
294 if self.variables.contains_key(name) {
295 self.exported.insert(name.to_string());
296 }
297 }
298
299 pub fn set_exported_var(&mut self, name: &str, value: String) {
301 self.set_var(name, value);
302 self.export_var(name);
303 }
304
305 pub fn get_env_for_child(&self) -> HashMap<String, String> {
307 let mut child_env = HashMap::new();
308
309 for (key, value) in env::vars() {
311 child_env.insert(key, value);
312 }
313
314 for var_name in &self.exported {
316 if let Some(value) = self.variables.get(var_name) {
317 child_env.insert(var_name.clone(), value.clone());
318 }
319 }
320
321 child_env
322 }
323
324 pub fn set_last_exit_code(&mut self, code: i32) {
326 self.last_exit_code = code;
327 }
328
329 pub fn set_script_name(&mut self, name: &str) {
331 self.script_name = name.to_string();
332 }
333
334 pub fn get_condensed_cwd(&self) -> String {
336 match env::current_dir() {
337 Ok(path) => {
338 let path_str = path.to_string_lossy();
339 let components: Vec<&str> = path_str.split('/').collect();
340 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
341 return "/".to_string();
342 }
343 let mut result = String::new();
344 for (i, comp) in components.iter().enumerate() {
345 if comp.is_empty() {
346 continue; }
348 if i == components.len() - 1 {
349 result.push('/');
350 result.push_str(comp);
351 } else {
352 result.push('/');
353 if let Some(first) = comp.chars().next() {
354 result.push(first);
355 }
356 }
357 }
358 if result.is_empty() {
359 "/".to_string()
360 } else {
361 result
362 }
363 }
364 Err(_) => "/?".to_string(), }
366 }
367
368 pub fn get_full_cwd(&self) -> String {
370 match env::current_dir() {
371 Ok(path) => path.to_string_lossy().to_string(),
372 Err(_) => "/?".to_string(), }
374 }
375
376 pub fn get_user_hostname(&self) -> String {
378 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
379
380 if let Ok(hostname) = env::var("HOSTNAME")
382 && !hostname.trim().is_empty()
383 {
384 return format!("{}@{}", user, hostname);
385 }
386
387 let hostname = match std::process::Command::new("hostname").output() {
389 Ok(output) if output.status.success() => {
390 String::from_utf8_lossy(&output.stdout).trim().to_string()
391 }
392 _ => "hostname".to_string(), };
394
395 if hostname != "hostname" {
397 unsafe {
398 env::set_var("HOSTNAME", &hostname);
399 }
400 }
401
402 format!("{}@{}", user, hostname)
403 }
404
405 pub fn get_prompt(&self) -> String {
407 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
408 let prompt_char = if user == "root" { "#" } else { "$" };
409 let cwd = if self.condensed_cwd {
410 self.get_condensed_cwd()
411 } else {
412 self.get_full_cwd()
413 };
414 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
415 }
416
417 pub fn set_alias(&mut self, name: &str, value: String) {
419 self.aliases.insert(name.to_string(), value);
420 }
421
422 pub fn get_alias(&self, name: &str) -> Option<&String> {
424 self.aliases.get(name)
425 }
426
427 pub fn remove_alias(&mut self, name: &str) {
429 self.aliases.remove(name);
430 }
431
432 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
434 &self.aliases
435 }
436
437 pub fn set_positional_params(&mut self, params: Vec<String>) {
439 self.positional_params = params;
440 }
441
442 #[allow(dead_code)]
444 pub fn get_positional_params(&self) -> &[String] {
445 &self.positional_params
446 }
447
448 pub fn shift_positional_params(&mut self, count: usize) {
450 if count > 0 {
451 for _ in 0..count {
452 if !self.positional_params.is_empty() {
453 self.positional_params.remove(0);
454 }
455 }
456 }
457 }
458
459 #[allow(dead_code)]
461 pub fn push_positional_param(&mut self, param: String) {
462 self.positional_params.push(param);
463 }
464
465 pub fn define_function(&mut self, name: String, body: Ast) {
467 self.functions.insert(name, body);
468 }
469
470 pub fn get_function(&self, name: &str) -> Option<&Ast> {
472 self.functions.get(name)
473 }
474
475 #[allow(dead_code)]
477 pub fn remove_function(&mut self, name: &str) {
478 self.functions.remove(name);
479 }
480
481 #[allow(dead_code)]
483 pub fn get_function_names(&self) -> Vec<&String> {
484 self.functions.keys().collect()
485 }
486
487 pub fn push_local_scope(&mut self) {
489 self.local_vars.push(HashMap::new());
490 }
491
492 pub fn pop_local_scope(&mut self) {
494 if !self.local_vars.is_empty() {
495 self.local_vars.pop();
496 }
497 }
498
499 pub fn set_local_var(&mut self, name: &str, value: String) {
501 if let Some(current_scope) = self.local_vars.last_mut() {
502 current_scope.insert(name.to_string(), value);
503 } else {
504 self.set_var(name, value);
506 }
507 }
508
509 pub fn enter_function(&mut self) {
511 self.function_depth += 1;
512 if self.function_depth > self.local_vars.len() {
513 self.push_local_scope();
514 }
515 }
516
517 pub fn exit_function(&mut self) {
519 if self.function_depth > 0 {
520 self.function_depth -= 1;
521 if self.function_depth == self.local_vars.len() - 1 {
522 self.pop_local_scope();
523 }
524 }
525 }
526
527 pub fn set_return(&mut self, value: i32) {
529 self.returning = true;
530 self.return_value = Some(value);
531 }
532
533 pub fn clear_return(&mut self) {
535 self.returning = false;
536 self.return_value = None;
537 }
538
539 pub fn is_returning(&self) -> bool {
541 self.returning
542 }
543
544 pub fn get_return_value(&self) -> Option<i32> {
546 self.return_value
547 }
548
549 pub fn enter_loop(&mut self) {
551 self.loop_depth += 1;
552 }
553
554 pub fn exit_loop(&mut self) {
556 if self.loop_depth > 0 {
557 self.loop_depth -= 1;
558 }
559 }
560
561 pub fn set_break(&mut self, level: usize) {
563 self.breaking = true;
564 self.break_level = level;
565 }
566
567 pub fn clear_break(&mut self) {
569 self.breaking = false;
570 self.break_level = 0;
571 }
572
573 pub fn is_breaking(&self) -> bool {
575 self.breaking
576 }
577
578 pub fn get_break_level(&self) -> usize {
580 self.break_level
581 }
582
583 pub fn decrement_break_level(&mut self) {
585 if self.break_level > 0 {
586 self.break_level -= 1;
587 }
588 if self.break_level == 0 {
589 self.breaking = false;
590 }
591 }
592
593 pub fn set_continue(&mut self, level: usize) {
595 self.continuing = true;
596 self.continue_level = level;
597 }
598
599 pub fn clear_continue(&mut self) {
601 self.continuing = false;
602 self.continue_level = 0;
603 }
604
605 pub fn is_continuing(&self) -> bool {
607 self.continuing
608 }
609
610 pub fn get_continue_level(&self) -> usize {
612 self.continue_level
613 }
614
615 pub fn decrement_continue_level(&mut self) {
617 if self.continue_level > 0 {
618 self.continue_level -= 1;
619 }
620 if self.continue_level == 0 {
621 self.continuing = false;
622 }
623 }
624
625 pub fn set_trap(&mut self, signal: &str, command: String) {
627 if let Ok(mut handlers) = self.trap_handlers.lock() {
628 handlers.insert(signal.to_uppercase(), command);
629 }
630 }
631
632 pub fn get_trap(&self, signal: &str) -> Option<String> {
634 if let Ok(handlers) = self.trap_handlers.lock() {
635 handlers.get(&signal.to_uppercase()).cloned()
636 } else {
637 None
638 }
639 }
640
641 pub fn remove_trap(&mut self, signal: &str) {
643 if let Ok(mut handlers) = self.trap_handlers.lock() {
644 handlers.remove(&signal.to_uppercase());
645 }
646 }
647
648 pub fn get_all_traps(&self) -> HashMap<String, String> {
650 if let Ok(handlers) = self.trap_handlers.lock() {
651 handlers.clone()
652 } else {
653 HashMap::new()
654 }
655 }
656
657 #[allow(dead_code)]
659 pub fn clear_traps(&mut self) {
660 if let Ok(mut handlers) = self.trap_handlers.lock() {
661 handlers.clear();
662 }
663 }
664}
665
666impl Default for ShellState {
667 fn default() -> Self {
668 Self::new()
669 }
670}
671
672#[cfg(test)]
673mod tests;