1use std::cell::RefCell;
2use std::fs::File;
3use std::io::pipe;
4use std::process::{Command, Stdio};
5use std::rc::Rc;
6
7use super::parser::{Ast, ShellCommand};
8use super::state::ShellState;
9
10fn execute_and_capture_output(ast: Ast, shell_state: &mut ShellState) -> Result<String, String> {
13 let (reader, writer) = pipe().map_err(|e| format!("Failed to create pipe: {}", e))?;
15
16 match &ast {
21 Ast::Pipeline(commands) if commands.len() == 1 => {
22 let cmd = &commands[0];
23 if cmd.args.is_empty() {
24 return Ok(String::new());
25 }
26
27 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
29 let expanded_args = expand_wildcards(&var_expanded_args)
30 .map_err(|e| format!("Wildcard expansion failed: {}", e))?;
31
32 if expanded_args.is_empty() {
33 return Ok(String::new());
34 }
35
36 if shell_state.get_function(&expanded_args[0]).is_some() {
38 let previous_capture = shell_state.capture_output.clone();
40
41 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
43 shell_state.capture_output = Some(capture_buffer.clone());
44
45 let function_call_ast = Ast::FunctionCall {
47 name: expanded_args[0].clone(),
48 args: expanded_args[1..].to_vec(),
49 };
50
51 let exit_code = execute(function_call_ast, shell_state);
52
53 let captured = capture_buffer.borrow().clone();
55 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
56
57 shell_state.capture_output = previous_capture;
59
60 if exit_code == 0 {
61 Ok(output)
62 } else {
63 Err(format!("Function failed with exit code {}", exit_code))
64 }
65 } else if crate::builtins::is_builtin(&expanded_args[0]) {
66 let temp_cmd = ShellCommand {
67 args: expanded_args,
68 input: cmd.input.clone(),
69 output: None, append: None,
71 };
72
73 let exit_code = crate::builtins::execute_builtin(
75 &temp_cmd,
76 shell_state,
77 Some(Box::new(writer)),
78 );
79
80 drop(temp_cmd); let mut output = String::new();
83 use std::io::Read;
84 let mut reader = reader;
85 reader
86 .read_to_string(&mut output)
87 .map_err(|e| format!("Failed to read output: {}", e))?;
88
89 if exit_code == 0 {
90 Ok(output.trim_end().to_string())
91 } else {
92 Err(format!("Command failed with exit code {}", exit_code))
93 }
94 } else {
95 drop(writer); let mut command = Command::new(&expanded_args[0]);
99 command.args(&expanded_args[1..]);
100 command.stdout(Stdio::piped());
101 command.stderr(Stdio::null()); let child_env = shell_state.get_env_for_child();
105 command.env_clear();
106 for (key, value) in child_env {
107 command.env(key, value);
108 }
109
110 let output = command
111 .output()
112 .map_err(|e| format!("Failed to execute command: {}", e))?;
113
114 if output.status.success() {
115 Ok(String::from_utf8_lossy(&output.stdout)
116 .trim_end()
117 .to_string())
118 } else {
119 Err(format!(
120 "Command failed with exit code {}",
121 output.status.code().unwrap_or(1)
122 ))
123 }
124 }
125 }
126 _ => {
127 drop(writer);
131
132 Err("Complex command substitutions not yet fully supported".to_string())
135 }
136 }
137}
138
139fn expand_variables_in_args(args: &[String], shell_state: &mut ShellState) -> Vec<String> {
140 let mut expanded_args = Vec::new();
141
142 for arg in args {
143 let expanded_arg = expand_variables_in_string(arg, shell_state);
145 expanded_args.push(expanded_arg);
146 }
147
148 expanded_args
149}
150
151pub fn expand_variables_in_string(input: &str, shell_state: &mut ShellState) -> String {
152 let mut result = String::new();
153 let mut chars = input.chars().peekable();
154
155 while let Some(ch) = chars.next() {
156 if ch == '$' {
157 if let Some(&'(') = chars.peek() {
159 chars.next(); if let Some(&'(') = chars.peek() {
163 chars.next(); let mut arithmetic_expr = String::new();
166 let mut paren_depth = 1;
167 let mut found_closing = false;
168
169 while let Some(c) = chars.next() {
170 if c == '(' {
171 paren_depth += 1;
172 arithmetic_expr.push(c);
173 } else if c == ')' {
174 paren_depth -= 1;
175 if paren_depth == 0 {
176 if let Some(&')') = chars.peek() {
178 chars.next(); found_closing = true;
180 break;
181 } else {
182 result.push_str("$((");
184 result.push_str(&arithmetic_expr);
185 result.push(')');
186 break;
187 }
188 }
189 arithmetic_expr.push(c);
190 } else {
191 arithmetic_expr.push(c);
192 }
193 }
194
195 if found_closing {
196 let mut expanded_expr = String::new();
200 let mut expr_chars = arithmetic_expr.chars().peekable();
201
202 while let Some(ch) = expr_chars.next() {
203 if ch == '$' {
204 let mut var_name = String::new();
206 if let Some(&c) = expr_chars.peek() {
207 if c == '?'
208 || c == '$'
209 || c == '0'
210 || c == '#'
211 || c == '*'
212 || c == '@'
213 || c.is_ascii_digit()
214 {
215 var_name.push(c);
216 expr_chars.next();
217 } else {
218 while let Some(&c) = expr_chars.peek() {
219 if c.is_alphanumeric() || c == '_' {
220 var_name.push(c);
221 expr_chars.next();
222 } else {
223 break;
224 }
225 }
226 }
227 }
228
229 if !var_name.is_empty() {
230 if let Some(value) = shell_state.get_var(&var_name) {
231 expanded_expr.push_str(&value);
232 } else {
233 expanded_expr.push('0');
235 }
236 } else {
237 expanded_expr.push('$');
238 }
239 } else {
240 expanded_expr.push(ch);
241 }
242 }
243
244 match crate::arithmetic::evaluate_arithmetic_expression(
245 &expanded_expr,
246 shell_state,
247 ) {
248 Ok(value) => {
249 result.push_str(&value.to_string());
250 }
251 Err(e) => {
252 if shell_state.colors_enabled {
254 result.push_str(&format!(
255 "{}arithmetic error: {}{}",
256 shell_state.color_scheme.error, e, "\x1b[0m"
257 ));
258 } else {
259 result.push_str(&format!("arithmetic error: {}", e));
260 }
261 }
262 }
263 } else {
264 result.push_str("$((");
266 result.push_str(&arithmetic_expr);
267 }
269 continue;
270 }
271
272 let mut sub_command = String::new();
274 let mut paren_depth = 1;
275
276 for c in chars.by_ref() {
277 if c == '(' {
278 paren_depth += 1;
279 sub_command.push(c);
280 } else if c == ')' {
281 paren_depth -= 1;
282 if paren_depth == 0 {
283 break;
284 }
285 sub_command.push(c);
286 } else {
287 sub_command.push(c);
288 }
289 }
290
291 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
294 let expanded_tokens = match crate::lexer::expand_aliases(
296 tokens,
297 shell_state,
298 &mut std::collections::HashSet::new(),
299 ) {
300 Ok(t) => t,
301 Err(_) => {
302 result.push_str("$(");
304 result.push_str(&sub_command);
305 result.push(')');
306 continue;
307 }
308 };
309
310 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
311 match execute_and_capture_output(ast, shell_state) {
313 Ok(output) => {
314 result.push_str(&output);
315 }
316 Err(_) => {
317 result.push_str("$(");
319 result.push_str(&sub_command);
320 result.push(')');
321 }
322 }
323 } else {
324 let tokens_str = sub_command.trim();
326 if tokens_str.contains(' ') {
327 let parts: Vec<&str> = tokens_str.split_whitespace().collect();
329 if let Some(first_token) = parts.first()
330 && shell_state.get_function(first_token).is_some()
331 {
332 let function_call = Ast::FunctionCall {
334 name: first_token.to_string(),
335 args: parts[1..].iter().map(|s| s.to_string()).collect(),
336 };
337 match execute_and_capture_output(function_call, shell_state) {
338 Ok(output) => {
339 result.push_str(&output);
340 continue;
341 }
342 Err(_) => {
343 }
345 }
346 }
347 }
348 result.push_str("$(");
350 result.push_str(&sub_command);
351 result.push(')');
352 }
353 } else {
354 result.push_str("$(");
356 result.push_str(&sub_command);
357 result.push(')');
358 }
359 } else {
360 let mut var_name = String::new();
362 let mut next_ch = chars.peek();
363
364 if let Some(&c) = next_ch {
366 if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' {
367 var_name.push(c);
368 chars.next(); } else if c.is_ascii_digit() {
370 var_name.push(c);
372 chars.next();
373 } else {
374 while let Some(&c) = next_ch {
376 if c.is_alphanumeric() || c == '_' {
377 var_name.push(c);
378 chars.next(); next_ch = chars.peek();
380 } else {
381 break;
382 }
383 }
384 }
385 }
386
387 if !var_name.is_empty() {
388 if let Some(value) = shell_state.get_var(&var_name) {
389 result.push_str(&value);
390 } else {
391 if var_name.chars().next().unwrap().is_ascii_digit()
394 || var_name == "?"
395 || var_name == "$"
396 || var_name == "0"
397 || var_name == "#"
398 || var_name == "*"
399 || var_name == "@"
400 {
401 } else {
403 result.push('$');
405 result.push_str(&var_name);
406 }
407 }
408 } else {
409 result.push('$');
410 }
411 }
412 } else if ch == '`' {
413 let mut sub_command = String::new();
415
416 for c in chars.by_ref() {
417 if c == '`' {
418 break;
419 }
420 sub_command.push(c);
421 }
422
423 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
425 let expanded_tokens = match crate::lexer::expand_aliases(
427 tokens,
428 shell_state,
429 &mut std::collections::HashSet::new(),
430 ) {
431 Ok(t) => t,
432 Err(_) => {
433 result.push('`');
435 result.push_str(&sub_command);
436 result.push('`');
437 continue;
438 }
439 };
440
441 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
442 match execute_and_capture_output(ast, shell_state) {
444 Ok(output) => {
445 result.push_str(&output);
446 }
447 Err(_) => {
448 result.push('`');
450 result.push_str(&sub_command);
451 result.push('`');
452 }
453 }
454 } else {
455 result.push('`');
457 result.push_str(&sub_command);
458 result.push('`');
459 }
460 } else {
461 result.push('`');
463 result.push_str(&sub_command);
464 result.push('`');
465 }
466 } else {
467 result.push(ch);
468 }
469 }
470
471 result
472}
473
474fn expand_wildcards(args: &[String]) -> Result<Vec<String>, String> {
475 let mut expanded_args = Vec::new();
476
477 for arg in args {
478 if arg.contains('*') || arg.contains('?') || arg.contains('[') {
479 match glob::glob(arg) {
481 Ok(paths) => {
482 let mut matches: Vec<String> = paths
483 .filter_map(|p| p.ok())
484 .map(|p| p.to_string_lossy().to_string())
485 .collect();
486 if matches.is_empty() {
487 expanded_args.push(arg.clone());
489 } else {
490 matches.sort();
492 expanded_args.extend(matches);
493 }
494 }
495 Err(_e) => {
496 expanded_args.push(arg.clone());
498 }
499 }
500 } else {
501 expanded_args.push(arg.clone());
502 }
503 }
504 Ok(expanded_args)
505}
506
507pub fn execute_trap_handler(trap_cmd: &str, shell_state: &mut ShellState) -> i32 {
510 let saved_exit_code = shell_state.last_exit_code;
512
513 let result = match crate::lexer::lex(trap_cmd, shell_state) {
519 Ok(tokens) => {
520 match crate::lexer::expand_aliases(
521 tokens,
522 shell_state,
523 &mut std::collections::HashSet::new(),
524 ) {
525 Ok(expanded_tokens) => {
526 match crate::parser::parse(expanded_tokens) {
527 Ok(ast) => execute(ast, shell_state),
528 Err(_) => {
529 saved_exit_code
531 }
532 }
533 }
534 Err(_) => {
535 saved_exit_code
537 }
538 }
539 }
540 Err(_) => {
541 saved_exit_code
543 }
544 };
545
546 shell_state.last_exit_code = saved_exit_code;
548
549 result
550}
551
552pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
553 match ast {
554 Ast::Assignment { var, value } => {
555 let expanded_value = expand_variables_in_string(&value, shell_state);
557 shell_state.set_var(&var, expanded_value);
558 0
559 }
560 Ast::LocalAssignment { var, value } => {
561 let expanded_value = expand_variables_in_string(&value, shell_state);
563 shell_state.set_local_var(&var, expanded_value);
564 0
565 }
566 Ast::Pipeline(commands) => {
567 if commands.is_empty() {
568 return 0;
569 }
570
571 if commands.len() == 1 {
572 execute_single_command(&commands[0], shell_state)
574 } else {
575 execute_pipeline(&commands, shell_state)
577 }
578 }
579 Ast::Sequence(asts) => {
580 let mut exit_code = 0;
581 for ast in asts {
582 exit_code = execute(ast, shell_state);
583
584 if shell_state.is_returning() {
586 return exit_code;
587 }
588
589 if shell_state.exit_requested {
591 return shell_state.exit_code;
592 }
593 }
594 exit_code
595 }
596 Ast::If {
597 branches,
598 else_branch,
599 } => {
600 for (condition, then_branch) in branches {
601 let cond_exit = execute(*condition, shell_state);
602 if cond_exit == 0 {
603 let exit_code = execute(*then_branch, shell_state);
604
605 if shell_state.is_returning() {
607 return exit_code;
608 }
609
610 return exit_code;
611 }
612 }
613 if let Some(else_b) = else_branch {
614 let exit_code = execute(*else_b, shell_state);
615
616 if shell_state.is_returning() {
618 return exit_code;
619 }
620
621 exit_code
622 } else {
623 0
624 }
625 }
626 Ast::Case {
627 word,
628 cases,
629 default,
630 } => {
631 for (patterns, branch) in cases {
632 for pattern in &patterns {
633 if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
634 if glob_pattern.matches(&word) {
635 let exit_code = execute(branch, shell_state);
636
637 if shell_state.is_returning() {
639 return exit_code;
640 }
641
642 return exit_code;
643 }
644 } else {
645 if &word == pattern {
647 let exit_code = execute(branch, shell_state);
648
649 if shell_state.is_returning() {
651 return exit_code;
652 }
653
654 return exit_code;
655 }
656 }
657 }
658 }
659 if let Some(def) = default {
660 let exit_code = execute(*def, shell_state);
661
662 if shell_state.is_returning() {
664 return exit_code;
665 }
666
667 exit_code
668 } else {
669 0
670 }
671 }
672 Ast::For {
673 variable,
674 items,
675 body,
676 } => {
677 let mut exit_code = 0;
678
679 for item in items {
681 crate::state::process_pending_signals(shell_state);
683
684 if shell_state.exit_requested {
686 return shell_state.exit_code;
687 }
688
689 shell_state.set_var(&variable, item.clone());
691
692 exit_code = execute(*body.clone(), shell_state);
694
695 if shell_state.is_returning() {
697 return exit_code;
698 }
699
700 if shell_state.exit_requested {
702 return shell_state.exit_code;
703 }
704 }
705
706 exit_code
707 }
708 Ast::While { condition, body } => {
709 let mut exit_code = 0;
710
711 loop {
713 let cond_exit = execute(*condition.clone(), shell_state);
715
716 if shell_state.is_returning() {
718 return cond_exit;
719 }
720
721 if shell_state.exit_requested {
723 return shell_state.exit_code;
724 }
725
726 if cond_exit != 0 {
728 break;
729 }
730
731 exit_code = execute(*body.clone(), shell_state);
733
734 if shell_state.is_returning() {
736 return exit_code;
737 }
738
739 if shell_state.exit_requested {
741 return shell_state.exit_code;
742 }
743 }
744
745 exit_code
746 }
747 Ast::FunctionDefinition { name, body } => {
748 shell_state.define_function(name.clone(), *body);
750 0
751 }
752 Ast::FunctionCall { name, args } => {
753 if let Some(function_body) = shell_state.get_function(&name).cloned() {
754 if shell_state.function_depth >= shell_state.max_recursion_depth {
756 eprintln!(
757 "Function recursion limit ({}) exceeded",
758 shell_state.max_recursion_depth
759 );
760 return 1;
761 }
762
763 shell_state.enter_function();
765
766 let old_positional = shell_state.positional_params.clone();
768
769 shell_state.set_positional_params(args.clone());
771
772 let exit_code = execute(function_body, shell_state);
774
775 if shell_state.is_returning() {
777 let return_value = shell_state.get_return_value().unwrap_or(0);
778
779 shell_state.set_positional_params(old_positional);
781
782 shell_state.exit_function();
784
785 shell_state.clear_return();
787
788 return return_value;
790 }
791
792 shell_state.set_positional_params(old_positional);
794
795 shell_state.exit_function();
797
798 exit_code
799 } else {
800 eprintln!("Function '{}' not found", name);
801 1
802 }
803 }
804 Ast::Return { value } => {
805 if shell_state.function_depth == 0 {
807 eprintln!("Return statement outside of function");
808 return 1;
809 }
810
811 let exit_code = if let Some(ref val) = value {
813 val.parse::<i32>().unwrap_or(0)
814 } else {
815 0
816 };
817
818 shell_state.set_return(exit_code);
820
821 exit_code
823 }
824 Ast::And { left, right } => {
825 let left_exit = execute(*left, shell_state);
827
828 if shell_state.is_returning() {
830 return left_exit;
831 }
832
833 if left_exit == 0 {
835 execute(*right, shell_state)
836 } else {
837 left_exit
838 }
839 }
840 Ast::Or { left, right } => {
841 let left_exit = execute(*left, shell_state);
843
844 if shell_state.is_returning() {
846 return left_exit;
847 }
848
849 if left_exit != 0 {
851 execute(*right, shell_state)
852 } else {
853 left_exit
854 }
855 }
856 }
857}
858
859fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
860 if cmd.args.is_empty() {
861 return 0;
862 }
863
864 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
866 let expanded_args = match expand_wildcards(&var_expanded_args) {
867 Ok(args) => args,
868 Err(_) => return 1,
869 };
870
871 if expanded_args.is_empty() {
872 return 0;
873 }
874
875 if shell_state.get_function(&expanded_args[0]).is_some() {
877 let function_call = Ast::FunctionCall {
879 name: expanded_args[0].clone(),
880 args: expanded_args[1..].to_vec(),
881 };
882 return execute(function_call, shell_state);
883 }
884
885 if crate::builtins::is_builtin(&expanded_args[0]) {
886 let temp_cmd = ShellCommand {
888 args: expanded_args,
889 input: cmd.input.clone(),
890 output: cmd.output.clone(),
891 append: cmd.append.clone(),
892 };
893
894 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
896 struct CaptureWriter {
898 buffer: Rc<RefCell<Vec<u8>>>,
899 }
900 impl std::io::Write for CaptureWriter {
901 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
902 self.buffer.borrow_mut().extend_from_slice(buf);
903 Ok(buf.len())
904 }
905 fn flush(&mut self) -> std::io::Result<()> {
906 Ok(())
907 }
908 }
909 let writer = CaptureWriter {
910 buffer: capture_buffer.clone(),
911 };
912 crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
913 } else {
914 crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
915 }
916 } else {
917 let mut command = Command::new(&expanded_args[0]);
918 command.args(&expanded_args[1..]);
919
920 let child_env = shell_state.get_env_for_child();
922 command.env_clear();
923 for (key, value) in child_env {
924 command.env(key, value);
925 }
926
927 let capturing = shell_state.capture_output.is_some();
929 if capturing {
930 command.stdout(Stdio::piped());
931 }
932
933 if let Some(ref input_file) = cmd.input {
935 let expanded_input = expand_variables_in_string(input_file, shell_state);
936 match File::open(&expanded_input) {
937 Ok(file) => {
938 command.stdin(Stdio::from(file));
939 }
940 Err(e) => {
941 if shell_state.colors_enabled {
942 eprintln!(
943 "{}Error opening input file '{}{}",
944 shell_state.color_scheme.error,
945 input_file,
946 &format!("': {}\x1b[0m", e)
947 );
948 } else {
949 eprintln!("Error opening input file '{}': {}", input_file, e);
950 }
951 return 1;
952 }
953 }
954 }
955
956 if let Some(ref output_file) = cmd.output {
958 let expanded_output = expand_variables_in_string(output_file, shell_state);
959 match File::create(&expanded_output) {
960 Ok(file) => {
961 command.stdout(Stdio::from(file));
962 }
963 Err(e) => {
964 if shell_state.colors_enabled {
965 eprintln!(
966 "{}Error creating output file '{}{}",
967 shell_state.color_scheme.error,
968 output_file,
969 &format!("': {}\x1b[0m", e)
970 );
971 } else {
972 eprintln!("Error creating output file '{}': {}", output_file, e);
973 }
974 return 1;
975 }
976 }
977 } else if let Some(ref append_file) = cmd.append {
978 let expanded_append = expand_variables_in_string(append_file, shell_state);
979 match File::options()
980 .append(true)
981 .create(true)
982 .open(&expanded_append)
983 {
984 Ok(file) => {
985 command.stdout(Stdio::from(file));
986 }
987 Err(e) => {
988 if shell_state.colors_enabled {
989 eprintln!(
990 "{}Error opening append file '{}{}",
991 shell_state.color_scheme.error,
992 append_file,
993 &format!("': {}\x1b[0m", e)
994 );
995 } else {
996 eprintln!("Error opening append file '{}': {}", append_file, e);
997 }
998 return 1;
999 }
1000 }
1001 }
1002
1003 match command.spawn() {
1004 Ok(mut child) => {
1005 if capturing && let Some(mut stdout) = child.stdout.take() {
1007 use std::io::Read;
1008 let mut output = Vec::new();
1009 if stdout.read_to_end(&mut output).is_ok()
1010 && let Some(ref capture_buffer) = shell_state.capture_output
1011 {
1012 capture_buffer.borrow_mut().extend_from_slice(&output);
1013 }
1014 }
1015
1016 match child.wait() {
1017 Ok(status) => status.code().unwrap_or(0),
1018 Err(e) => {
1019 if shell_state.colors_enabled {
1020 eprintln!(
1021 "{}Error waiting for command: {}\x1b[0m",
1022 shell_state.color_scheme.error, e
1023 );
1024 } else {
1025 eprintln!("Error waiting for command: {}", e);
1026 }
1027 1
1028 }
1029 }
1030 }
1031 Err(e) => {
1032 if shell_state.colors_enabled {
1033 eprintln!(
1034 "{}Command spawn error: {}\x1b[0m",
1035 shell_state.color_scheme.error, e
1036 );
1037 } else {
1038 eprintln!("Command spawn error: {}", e);
1039 }
1040 1
1041 }
1042 }
1043 }
1044}
1045
1046fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
1047 let mut exit_code = 0;
1048 let mut previous_stdout = None;
1049
1050 for (i, cmd) in commands.iter().enumerate() {
1051 if cmd.args.is_empty() {
1052 continue;
1053 }
1054
1055 let is_last = i == commands.len() - 1;
1056
1057 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1059 let expanded_args = match expand_wildcards(&var_expanded_args) {
1060 Ok(args) => args,
1061 Err(_) => return 1,
1062 };
1063
1064 if expanded_args.is_empty() {
1065 continue;
1066 }
1067
1068 if crate::builtins::is_builtin(&expanded_args[0]) {
1069 let temp_cmd = ShellCommand {
1072 args: expanded_args,
1073 input: cmd.input.clone(),
1074 output: cmd.output.clone(),
1075 append: cmd.append.clone(),
1076 };
1077 if !is_last {
1078 let (reader, writer) = match pipe() {
1080 Ok(p) => p,
1081 Err(e) => {
1082 if shell_state.colors_enabled {
1083 eprintln!(
1084 "{}Error creating pipe for builtin: {}\x1b[0m",
1085 shell_state.color_scheme.error, e
1086 );
1087 } else {
1088 eprintln!("Error creating pipe for builtin: {}", e);
1089 }
1090 return 1;
1091 }
1092 };
1093 exit_code = crate::builtins::execute_builtin(
1095 &temp_cmd,
1096 shell_state,
1097 Some(Box::new(writer)),
1098 );
1099 previous_stdout = Some(Stdio::from(reader));
1101 } else {
1102 exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
1104 previous_stdout = None;
1105 }
1106 } else {
1107 let mut command = Command::new(&expanded_args[0]);
1108 command.args(&expanded_args[1..]);
1109
1110 let child_env = shell_state.get_env_for_child();
1112 command.env_clear();
1113 for (key, value) in child_env {
1114 command.env(key, value);
1115 }
1116
1117 if let Some(prev) = previous_stdout.take() {
1119 command.stdin(prev);
1120 }
1121
1122 if !is_last {
1124 command.stdout(Stdio::piped());
1125 }
1126
1127 if i == 0
1129 && let Some(ref input_file) = cmd.input
1130 {
1131 let expanded_input = expand_variables_in_string(input_file, shell_state);
1132 match File::open(&expanded_input) {
1133 Ok(file) => {
1134 command.stdin(Stdio::from(file));
1135 }
1136 Err(e) => {
1137 if shell_state.colors_enabled {
1138 eprintln!(
1139 "{}Error opening input file '{}{}",
1140 shell_state.color_scheme.error,
1141 input_file,
1142 &format!("': {}\x1b[0m", e)
1143 );
1144 } else {
1145 eprintln!("Error opening input file '{}': {}", input_file, e);
1146 }
1147 return 1;
1148 }
1149 }
1150 }
1151
1152 if is_last {
1154 if let Some(ref output_file) = cmd.output {
1155 let expanded_output = expand_variables_in_string(output_file, shell_state);
1156 match File::create(&expanded_output) {
1157 Ok(file) => {
1158 command.stdout(Stdio::from(file));
1159 }
1160 Err(e) => {
1161 if shell_state.colors_enabled {
1162 eprintln!(
1163 "{}Error creating output file '{}{}",
1164 shell_state.color_scheme.error,
1165 output_file,
1166 &format!("': {}\x1b[0m", e)
1167 );
1168 } else {
1169 eprintln!("Error creating output file '{}': {}", output_file, e);
1170 }
1171 return 1;
1172 }
1173 }
1174 } else if let Some(ref append_file) = cmd.append {
1175 let expanded_append = expand_variables_in_string(append_file, shell_state);
1176 match File::options()
1177 .append(true)
1178 .create(true)
1179 .open(&expanded_append)
1180 {
1181 Ok(file) => {
1182 command.stdout(Stdio::from(file));
1183 }
1184 Err(e) => {
1185 if shell_state.colors_enabled {
1186 eprintln!(
1187 "{}Error opening append file '{}{}",
1188 shell_state.color_scheme.error,
1189 append_file,
1190 &format!("': {}\x1b[0m", e)
1191 );
1192 } else {
1193 eprintln!("Error opening append file '{}': {}", append_file, e);
1194 }
1195 return 1;
1196 }
1197 }
1198 }
1199 }
1200
1201 match command.spawn() {
1202 Ok(mut child) => {
1203 if !is_last {
1204 previous_stdout = child.stdout.take().map(Stdio::from);
1205 }
1206 match child.wait() {
1207 Ok(status) => {
1208 exit_code = status.code().unwrap_or(0);
1209 }
1210 Err(e) => {
1211 if shell_state.colors_enabled {
1212 eprintln!(
1213 "{}Error waiting for command: {}\x1b[0m",
1214 shell_state.color_scheme.error, e
1215 );
1216 } else {
1217 eprintln!("Error waiting for command: {}", e);
1218 }
1219 exit_code = 1;
1220 }
1221 }
1222 }
1223 Err(e) => {
1224 if shell_state.colors_enabled {
1225 eprintln!(
1226 "{}Error spawning command '{}{}",
1227 shell_state.color_scheme.error,
1228 expanded_args[0],
1229 &format!("': {}\x1b[0m", e)
1230 );
1231 } else {
1232 eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1233 }
1234 exit_code = 1;
1235 }
1236 }
1237 }
1238 }
1239
1240 exit_code
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245 use super::*;
1246
1247 #[test]
1248 fn test_execute_single_command_builtin() {
1249 let cmd = ShellCommand {
1250 args: vec!["true".to_string()],
1251 input: None,
1252 output: None,
1253 append: None,
1254 };
1255 let mut shell_state = ShellState::new();
1256 let exit_code = execute_single_command(&cmd, &mut shell_state);
1257 assert_eq!(exit_code, 0);
1258 }
1259
1260 #[test]
1262 fn test_execute_single_command_external() {
1263 let cmd = ShellCommand {
1264 args: vec!["true".to_string()], input: None,
1266 output: None,
1267 append: None,
1268 };
1269 let mut shell_state = ShellState::new();
1270 let exit_code = execute_single_command(&cmd, &mut shell_state);
1271 assert_eq!(exit_code, 0);
1272 }
1273
1274 #[test]
1275 fn test_execute_single_command_external_nonexistent() {
1276 let cmd = ShellCommand {
1277 args: vec!["nonexistent_command".to_string()],
1278 input: None,
1279 output: None,
1280 append: None,
1281 };
1282 let mut shell_state = ShellState::new();
1283 let exit_code = execute_single_command(&cmd, &mut shell_state);
1284 assert_eq!(exit_code, 1); }
1286
1287 #[test]
1288 fn test_execute_pipeline() {
1289 let commands = vec![
1290 ShellCommand {
1291 args: vec!["printf".to_string(), "hello".to_string()],
1292 input: None,
1293 output: None,
1294 append: None,
1295 },
1296 ShellCommand {
1297 args: vec!["cat".to_string()], input: None,
1299 output: None,
1300 append: None,
1301 },
1302 ];
1303 let mut shell_state = ShellState::new();
1304 let exit_code = execute_pipeline(&commands, &mut shell_state);
1305 assert_eq!(exit_code, 0);
1306 }
1307
1308 #[test]
1309 fn test_execute_empty_pipeline() {
1310 let commands = vec![];
1311 let mut shell_state = ShellState::new();
1312 let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
1313 assert_eq!(exit_code, 0);
1314 }
1315
1316 #[test]
1317 fn test_execute_single_command() {
1318 let ast = Ast::Pipeline(vec![ShellCommand {
1319 args: vec!["true".to_string()],
1320 input: None,
1321 output: None,
1322 append: None,
1323 }]);
1324 let mut shell_state = ShellState::new();
1325 let exit_code = execute(ast, &mut shell_state);
1326 assert_eq!(exit_code, 0);
1327 }
1328
1329 #[test]
1330 fn test_execute_function_definition() {
1331 let ast = Ast::FunctionDefinition {
1332 name: "test_func".to_string(),
1333 body: Box::new(Ast::Pipeline(vec![ShellCommand {
1334 args: vec!["echo".to_string(), "hello".to_string()],
1335 input: None,
1336 output: None,
1337 append: None,
1338 }])),
1339 };
1340 let mut shell_state = ShellState::new();
1341 let exit_code = execute(ast, &mut shell_state);
1342 assert_eq!(exit_code, 0);
1343
1344 assert!(shell_state.get_function("test_func").is_some());
1346 }
1347
1348 #[test]
1349 fn test_execute_function_call() {
1350 let mut shell_state = ShellState::new();
1352 shell_state.define_function(
1353 "test_func".to_string(),
1354 Ast::Pipeline(vec![ShellCommand {
1355 args: vec!["echo".to_string(), "hello".to_string()],
1356 input: None,
1357 output: None,
1358 append: None,
1359 }]),
1360 );
1361
1362 let ast = Ast::FunctionCall {
1364 name: "test_func".to_string(),
1365 args: vec![],
1366 };
1367 let exit_code = execute(ast, &mut shell_state);
1368 assert_eq!(exit_code, 0);
1369 }
1370
1371 #[test]
1372 fn test_execute_function_call_with_args() {
1373 let mut shell_state = ShellState::new();
1375 shell_state.define_function(
1376 "test_func".to_string(),
1377 Ast::Pipeline(vec![ShellCommand {
1378 args: vec!["echo".to_string(), "arg1".to_string()],
1379 input: None,
1380 output: None,
1381 append: None,
1382 }]),
1383 );
1384
1385 let ast = Ast::FunctionCall {
1387 name: "test_func".to_string(),
1388 args: vec!["hello".to_string()],
1389 };
1390 let exit_code = execute(ast, &mut shell_state);
1391 assert_eq!(exit_code, 0);
1392 }
1393
1394 #[test]
1395 fn test_execute_nonexistent_function() {
1396 let mut shell_state = ShellState::new();
1397 let ast = Ast::FunctionCall {
1398 name: "nonexistent".to_string(),
1399 args: vec![],
1400 };
1401 let exit_code = execute(ast, &mut shell_state);
1402 assert_eq!(exit_code, 1); }
1404
1405 #[test]
1406 fn test_execute_function_integration() {
1407 let mut shell_state = ShellState::new();
1409
1410 let define_ast = Ast::FunctionDefinition {
1412 name: "hello".to_string(),
1413 body: Box::new(Ast::Pipeline(vec![ShellCommand {
1414 args: vec!["printf".to_string(), "Hello from function".to_string()],
1415 input: None,
1416 output: None,
1417 append: None,
1418 }])),
1419 };
1420 let exit_code = execute(define_ast, &mut shell_state);
1421 assert_eq!(exit_code, 0);
1422
1423 let call_ast = Ast::FunctionCall {
1425 name: "hello".to_string(),
1426 args: vec![],
1427 };
1428 let exit_code = execute(call_ast, &mut shell_state);
1429 assert_eq!(exit_code, 0);
1430 }
1431
1432 #[test]
1433 fn test_execute_function_with_local_variables() {
1434 let mut shell_state = ShellState::new();
1435
1436 shell_state.set_var("global_var", "global_value".to_string());
1438
1439 let define_ast = Ast::FunctionDefinition {
1441 name: "test_func".to_string(),
1442 body: Box::new(Ast::Sequence(vec![
1443 Ast::LocalAssignment {
1444 var: "local_var".to_string(),
1445 value: "local_value".to_string(),
1446 },
1447 Ast::Assignment {
1448 var: "global_var".to_string(),
1449 value: "modified_in_function".to_string(),
1450 },
1451 Ast::Pipeline(vec![ShellCommand {
1452 args: vec!["printf".to_string(), "success".to_string()],
1453 input: None,
1454 output: None,
1455 append: None,
1456 }]),
1457 ])),
1458 };
1459 let exit_code = execute(define_ast, &mut shell_state);
1460 assert_eq!(exit_code, 0);
1461
1462 assert_eq!(
1464 shell_state.get_var("global_var"),
1465 Some("global_value".to_string())
1466 );
1467
1468 let call_ast = Ast::FunctionCall {
1470 name: "test_func".to_string(),
1471 args: vec![],
1472 };
1473 let exit_code = execute(call_ast, &mut shell_state);
1474 assert_eq!(exit_code, 0);
1475
1476 assert_eq!(
1478 shell_state.get_var("global_var"),
1479 Some("modified_in_function".to_string())
1480 );
1481 }
1482
1483 #[test]
1484 fn test_execute_nested_function_calls() {
1485 let mut shell_state = ShellState::new();
1486
1487 shell_state.set_var("global_var", "global".to_string());
1489
1490 let outer_func = Ast::FunctionDefinition {
1492 name: "outer".to_string(),
1493 body: Box::new(Ast::Sequence(vec![
1494 Ast::Assignment {
1495 var: "global_var".to_string(),
1496 value: "outer_modified".to_string(),
1497 },
1498 Ast::FunctionCall {
1499 name: "inner".to_string(),
1500 args: vec![],
1501 },
1502 Ast::Pipeline(vec![ShellCommand {
1503 args: vec!["printf".to_string(), "outer_done".to_string()],
1504 input: None,
1505 output: None,
1506 append: None,
1507 }]),
1508 ])),
1509 };
1510
1511 let inner_func = Ast::FunctionDefinition {
1513 name: "inner".to_string(),
1514 body: Box::new(Ast::Sequence(vec![
1515 Ast::Assignment {
1516 var: "global_var".to_string(),
1517 value: "inner_modified".to_string(),
1518 },
1519 Ast::Pipeline(vec![ShellCommand {
1520 args: vec!["printf".to_string(), "inner_done".to_string()],
1521 input: None,
1522 output: None,
1523 append: None,
1524 }]),
1525 ])),
1526 };
1527
1528 execute(outer_func, &mut shell_state);
1530 execute(inner_func, &mut shell_state);
1531
1532 shell_state.set_var("global_var", "initial".to_string());
1534
1535 let call_ast = Ast::FunctionCall {
1537 name: "outer".to_string(),
1538 args: vec![],
1539 };
1540 let exit_code = execute(call_ast, &mut shell_state);
1541 assert_eq!(exit_code, 0);
1542
1543 assert_eq!(
1546 shell_state.get_var("global_var"),
1547 Some("inner_modified".to_string())
1548 );
1549 }
1550}