1use std::cell::RefCell;
2use std::fs::File;
3use std::io::{BufRead, BufReader, 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) => {
22 if commands.is_empty() {
24 return Ok(String::new());
25 }
26
27 if commands.len() == 1 {
28 let cmd = &commands[0];
30 if cmd.args.is_empty() {
31 return Ok(String::new());
32 }
33
34 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
36 let expanded_args = expand_wildcards(&var_expanded_args)
37 .map_err(|e| format!("Wildcard expansion failed: {}", e))?;
38
39 if expanded_args.is_empty() {
40 return Ok(String::new());
41 }
42
43 if shell_state.get_function(&expanded_args[0]).is_some() {
45 let previous_capture = shell_state.capture_output.clone();
47
48 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
50 shell_state.capture_output = Some(capture_buffer.clone());
51
52 let function_call_ast = Ast::FunctionCall {
54 name: expanded_args[0].clone(),
55 args: expanded_args[1..].to_vec(),
56 };
57
58 let exit_code = execute(function_call_ast, shell_state);
59
60 let captured = capture_buffer.borrow().clone();
62 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
63
64 shell_state.capture_output = previous_capture;
66
67 if exit_code == 0 {
68 Ok(output)
69 } else {
70 Err(format!("Function failed with exit code {}", exit_code))
71 }
72 } else if crate::builtins::is_builtin(&expanded_args[0]) {
73 let temp_cmd = ShellCommand {
74 args: expanded_args,
75 input: cmd.input.clone(),
76 output: None, append: None,
78 here_doc_delimiter: None,
79 here_doc_quoted: false,
80 here_string_content: None,
81 };
82
83 let exit_code = crate::builtins::execute_builtin(
85 &temp_cmd,
86 shell_state,
87 Some(Box::new(writer)),
88 );
89
90 drop(temp_cmd); let mut output = String::new();
93 use std::io::Read;
94 let mut reader = reader;
95 reader
96 .read_to_string(&mut output)
97 .map_err(|e| format!("Failed to read output: {}", e))?;
98
99 if exit_code == 0 {
100 Ok(output.trim_end().to_string())
101 } else {
102 Err(format!("Command failed with exit code {}", exit_code))
103 }
104 } else {
105 drop(writer); let mut command = Command::new(&expanded_args[0]);
109 command.args(&expanded_args[1..]);
110 command.stdout(Stdio::piped());
111 command.stderr(Stdio::null()); let child_env = shell_state.get_env_for_child();
115 command.env_clear();
116 for (key, value) in child_env {
117 command.env(key, value);
118 }
119
120 let output = command
121 .output()
122 .map_err(|e| format!("Failed to execute command: {}", e))?;
123
124 if output.status.success() {
125 Ok(String::from_utf8_lossy(&output.stdout)
126 .trim_end()
127 .to_string())
128 } else {
129 Err(format!(
130 "Command failed with exit code {}",
131 output.status.code().unwrap_or(1)
132 ))
133 }
134 }
135 } else {
136 drop(writer); let previous_capture = shell_state.capture_output.clone();
141
142 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
144 shell_state.capture_output = Some(capture_buffer.clone());
145
146 let exit_code = execute_pipeline(commands, shell_state);
148
149 let captured = capture_buffer.borrow().clone();
151 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
152
153 shell_state.capture_output = previous_capture;
155
156 if exit_code == 0 {
157 Ok(output)
158 } else {
159 Err(format!("Pipeline failed with exit code {}", exit_code))
160 }
161 }
162 }
163 _ => {
164 drop(writer);
166
167 let previous_capture = shell_state.capture_output.clone();
169
170 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
172 shell_state.capture_output = Some(capture_buffer.clone());
173
174 let exit_code = execute(ast, shell_state);
176
177 let captured = capture_buffer.borrow().clone();
179 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
180
181 shell_state.capture_output = previous_capture;
183
184 if exit_code == 0 {
185 Ok(output)
186 } else {
187 Err(format!("Command failed with exit code {}", exit_code))
188 }
189 }
190 }
191}
192
193fn expand_variables_in_args(args: &[String], shell_state: &mut ShellState) -> Vec<String> {
194 let mut expanded_args = Vec::new();
195
196 for arg in args {
197 let expanded_arg = expand_variables_in_string(arg, shell_state);
199 expanded_args.push(expanded_arg);
200 }
201
202 expanded_args
203}
204
205pub fn expand_variables_in_string(input: &str, shell_state: &mut ShellState) -> String {
206 let mut result = String::new();
207 let mut chars = input.chars().peekable();
208
209 while let Some(ch) = chars.next() {
210 if ch == '$' {
211 if let Some(&'(') = chars.peek() {
213 chars.next(); if let Some(&'(') = chars.peek() {
217 chars.next(); let mut arithmetic_expr = String::new();
220 let mut paren_depth = 1;
221 let mut found_closing = false;
222
223 while let Some(c) = chars.next() {
224 if c == '(' {
225 paren_depth += 1;
226 arithmetic_expr.push(c);
227 } else if c == ')' {
228 paren_depth -= 1;
229 if paren_depth == 0 {
230 if let Some(&')') = chars.peek() {
232 chars.next(); found_closing = true;
234 break;
235 } else {
236 result.push_str("$((");
238 result.push_str(&arithmetic_expr);
239 result.push(')');
240 break;
241 }
242 }
243 arithmetic_expr.push(c);
244 } else {
245 arithmetic_expr.push(c);
246 }
247 }
248
249 if found_closing {
250 let mut expanded_expr = String::new();
254 let mut expr_chars = arithmetic_expr.chars().peekable();
255
256 while let Some(ch) = expr_chars.next() {
257 if ch == '$' {
258 let mut var_name = String::new();
260 if let Some(&c) = expr_chars.peek() {
261 if c == '?'
262 || c == '$'
263 || c == '0'
264 || c == '#'
265 || c == '*'
266 || c == '@'
267 || c.is_ascii_digit()
268 {
269 var_name.push(c);
270 expr_chars.next();
271 } else {
272 while let Some(&c) = expr_chars.peek() {
273 if c.is_alphanumeric() || c == '_' {
274 var_name.push(c);
275 expr_chars.next();
276 } else {
277 break;
278 }
279 }
280 }
281 }
282
283 if !var_name.is_empty() {
284 if let Some(value) = shell_state.get_var(&var_name) {
285 expanded_expr.push_str(&value);
286 } else {
287 expanded_expr.push('0');
289 }
290 } else {
291 expanded_expr.push('$');
292 }
293 } else {
294 expanded_expr.push(ch);
295 }
296 }
297
298 match crate::arithmetic::evaluate_arithmetic_expression(
299 &expanded_expr,
300 shell_state,
301 ) {
302 Ok(value) => {
303 result.push_str(&value.to_string());
304 }
305 Err(e) => {
306 if shell_state.colors_enabled {
308 result.push_str(&format!(
309 "{}arithmetic error: {}{}",
310 shell_state.color_scheme.error, e, "\x1b[0m"
311 ));
312 } else {
313 result.push_str(&format!("arithmetic error: {}", e));
314 }
315 }
316 }
317 } else {
318 result.push_str("$((");
320 result.push_str(&arithmetic_expr);
321 }
323 continue;
324 }
325
326 let mut sub_command = String::new();
328 let mut paren_depth = 1;
329
330 for c in chars.by_ref() {
331 if c == '(' {
332 paren_depth += 1;
333 sub_command.push(c);
334 } else if c == ')' {
335 paren_depth -= 1;
336 if paren_depth == 0 {
337 break;
338 }
339 sub_command.push(c);
340 } else {
341 sub_command.push(c);
342 }
343 }
344
345 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
348 let expanded_tokens = match crate::lexer::expand_aliases(
350 tokens,
351 shell_state,
352 &mut std::collections::HashSet::new(),
353 ) {
354 Ok(t) => t,
355 Err(_) => {
356 result.push_str("$(");
358 result.push_str(&sub_command);
359 result.push(')');
360 continue;
361 }
362 };
363
364 match crate::parser::parse(expanded_tokens) {
365 Ok(ast) => {
366 match execute_and_capture_output(ast, shell_state) {
368 Ok(output) => {
369 result.push_str(&output);
370 }
371 Err(_) => {
372 result.push_str("$(");
374 result.push_str(&sub_command);
375 result.push(')');
376 }
377 }
378 }
379 Err(_parse_err) => {
380 let tokens_str = sub_command.trim();
382 if tokens_str.contains(' ') {
383 let parts: Vec<&str> = tokens_str.split_whitespace().collect();
385 if let Some(first_token) = parts.first()
386 && shell_state.get_function(first_token).is_some()
387 {
388 let function_call = Ast::FunctionCall {
390 name: first_token.to_string(),
391 args: parts[1..].iter().map(|s| s.to_string()).collect(),
392 };
393 match execute_and_capture_output(function_call, shell_state) {
394 Ok(output) => {
395 result.push_str(&output);
396 continue;
397 }
398 Err(_) => {
399 }
401 }
402 }
403 }
404 result.push_str("$(");
406 result.push_str(&sub_command);
407 result.push(')');
408 }
409 }
410 } else {
411 result.push_str("$(");
413 result.push_str(&sub_command);
414 result.push(')');
415 }
416 } else {
417 let mut var_name = String::new();
419 let mut next_ch = chars.peek();
420
421 if let Some(&c) = next_ch {
423 if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' {
424 var_name.push(c);
425 chars.next(); } else if c.is_ascii_digit() {
427 var_name.push(c);
429 chars.next();
430 } else {
431 while let Some(&c) = next_ch {
433 if c.is_alphanumeric() || c == '_' {
434 var_name.push(c);
435 chars.next(); next_ch = chars.peek();
437 } else {
438 break;
439 }
440 }
441 }
442 }
443
444 if !var_name.is_empty() {
445 if let Some(value) = shell_state.get_var(&var_name) {
446 result.push_str(&value);
447 } else {
448 if var_name.chars().next().unwrap().is_ascii_digit()
451 || var_name == "?"
452 || var_name == "$"
453 || var_name == "0"
454 || var_name == "#"
455 || var_name == "*"
456 || var_name == "@"
457 {
458 } else {
460 result.push('$');
462 result.push_str(&var_name);
463 }
464 }
465 } else {
466 result.push('$');
467 }
468 }
469 } else if ch == '`' {
470 let mut sub_command = String::new();
472
473 for c in chars.by_ref() {
474 if c == '`' {
475 break;
476 }
477 sub_command.push(c);
478 }
479
480 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
482 let expanded_tokens = match crate::lexer::expand_aliases(
484 tokens,
485 shell_state,
486 &mut std::collections::HashSet::new(),
487 ) {
488 Ok(t) => t,
489 Err(_) => {
490 result.push('`');
492 result.push_str(&sub_command);
493 result.push('`');
494 continue;
495 }
496 };
497
498 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
499 match execute_and_capture_output(ast, shell_state) {
501 Ok(output) => {
502 result.push_str(&output);
503 }
504 Err(_) => {
505 result.push('`');
507 result.push_str(&sub_command);
508 result.push('`');
509 }
510 }
511 } else {
512 result.push('`');
514 result.push_str(&sub_command);
515 result.push('`');
516 }
517 } else {
518 result.push('`');
520 result.push_str(&sub_command);
521 result.push('`');
522 }
523 } else {
524 result.push(ch);
525 }
526 }
527
528 result
529}
530
531fn expand_wildcards(args: &[String]) -> Result<Vec<String>, String> {
532 let mut expanded_args = Vec::new();
533
534 for arg in args {
535 if arg.contains('*') || arg.contains('?') || arg.contains('[') {
536 match glob::glob(arg) {
538 Ok(paths) => {
539 let mut matches: Vec<String> = paths
540 .filter_map(|p| p.ok())
541 .map(|p| p.to_string_lossy().to_string())
542 .collect();
543 if matches.is_empty() {
544 expanded_args.push(arg.clone());
546 } else {
547 matches.sort();
549 expanded_args.extend(matches);
550 }
551 }
552 Err(_e) => {
553 expanded_args.push(arg.clone());
555 }
556 }
557 } else {
558 expanded_args.push(arg.clone());
559 }
560 }
561 Ok(expanded_args)
562}
563
564fn collect_here_document_content(delimiter: &str, shell_state: &mut ShellState) -> String {
568 if let Some(content) = shell_state.pending_heredoc_content.take() {
570 return content;
571 }
572
573 let stdin = std::io::stdin();
575 let mut reader = BufReader::new(stdin.lock());
576 let mut content = String::new();
577 let mut line = String::new();
578
579 loop {
580 line.clear();
581 match reader.read_line(&mut line) {
582 Ok(0) => {
583 break;
585 }
586 Ok(_) => {
587 let line_content = line.trim_end();
589 if line_content == delimiter {
590 break;
592 } else {
593 content.push_str(&line);
595 }
596 }
597 Err(e) => {
598 if shell_state.colors_enabled {
599 eprintln!(
600 "{}Error reading here-document content: {}\x1b[0m",
601 shell_state.color_scheme.error, e
602 );
603 } else {
604 eprintln!("Error reading here-document content: {}", e);
605 }
606 break;
607 }
608 }
609 }
610
611 content
612}
613
614pub fn execute_trap_handler(trap_cmd: &str, shell_state: &mut ShellState) -> i32 {
617 let saved_exit_code = shell_state.last_exit_code;
619
620 let result = match crate::lexer::lex(trap_cmd, shell_state) {
626 Ok(tokens) => {
627 match crate::lexer::expand_aliases(
628 tokens,
629 shell_state,
630 &mut std::collections::HashSet::new(),
631 ) {
632 Ok(expanded_tokens) => {
633 match crate::parser::parse(expanded_tokens) {
634 Ok(ast) => execute(ast, shell_state),
635 Err(_) => {
636 saved_exit_code
638 }
639 }
640 }
641 Err(_) => {
642 saved_exit_code
644 }
645 }
646 }
647 Err(_) => {
648 saved_exit_code
650 }
651 };
652
653 shell_state.last_exit_code = saved_exit_code;
655
656 result
657}
658
659pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
660 match ast {
661 Ast::Assignment { var, value } => {
662 let expanded_value = expand_variables_in_string(&value, shell_state);
664 shell_state.set_var(&var, expanded_value);
665 0
666 }
667 Ast::LocalAssignment { var, value } => {
668 let expanded_value = expand_variables_in_string(&value, shell_state);
670 shell_state.set_local_var(&var, expanded_value);
671 0
672 }
673 Ast::Pipeline(commands) => {
674 if commands.is_empty() {
675 return 0;
676 }
677
678 if commands.len() == 1 {
679 execute_single_command(&commands[0], shell_state)
681 } else {
682 execute_pipeline(&commands, shell_state)
684 }
685 }
686 Ast::Sequence(asts) => {
687 let mut exit_code = 0;
688 for ast in asts {
689 exit_code = execute(ast, shell_state);
690
691 if shell_state.is_returning() {
693 return exit_code;
694 }
695
696 if shell_state.exit_requested {
698 return shell_state.exit_code;
699 }
700 }
701 exit_code
702 }
703 Ast::If {
704 branches,
705 else_branch,
706 } => {
707 for (condition, then_branch) in branches {
708 let cond_exit = execute(*condition, shell_state);
709 if cond_exit == 0 {
710 let exit_code = execute(*then_branch, shell_state);
711
712 if shell_state.is_returning() {
714 return exit_code;
715 }
716
717 return exit_code;
718 }
719 }
720 if let Some(else_b) = else_branch {
721 let exit_code = execute(*else_b, shell_state);
722
723 if shell_state.is_returning() {
725 return exit_code;
726 }
727
728 exit_code
729 } else {
730 0
731 }
732 }
733 Ast::Case {
734 word,
735 cases,
736 default,
737 } => {
738 for (patterns, branch) in cases {
739 for pattern in &patterns {
740 if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
741 if glob_pattern.matches(&word) {
742 let exit_code = execute(branch, shell_state);
743
744 if shell_state.is_returning() {
746 return exit_code;
747 }
748
749 return exit_code;
750 }
751 } else {
752 if &word == pattern {
754 let exit_code = execute(branch, shell_state);
755
756 if shell_state.is_returning() {
758 return exit_code;
759 }
760
761 return exit_code;
762 }
763 }
764 }
765 }
766 if let Some(def) = default {
767 let exit_code = execute(*def, shell_state);
768
769 if shell_state.is_returning() {
771 return exit_code;
772 }
773
774 exit_code
775 } else {
776 0
777 }
778 }
779 Ast::For {
780 variable,
781 items,
782 body,
783 } => {
784 let mut exit_code = 0;
785
786 for item in items {
788 crate::state::process_pending_signals(shell_state);
790
791 if shell_state.exit_requested {
793 return shell_state.exit_code;
794 }
795
796 shell_state.set_var(&variable, item.clone());
798
799 exit_code = execute(*body.clone(), shell_state);
801
802 if shell_state.is_returning() {
804 return exit_code;
805 }
806
807 if shell_state.exit_requested {
809 return shell_state.exit_code;
810 }
811 }
812
813 exit_code
814 }
815 Ast::While { condition, body } => {
816 let mut exit_code = 0;
817
818 loop {
820 let cond_exit = execute(*condition.clone(), shell_state);
822
823 if shell_state.is_returning() {
825 return cond_exit;
826 }
827
828 if shell_state.exit_requested {
830 return shell_state.exit_code;
831 }
832
833 if cond_exit != 0 {
835 break;
836 }
837
838 exit_code = execute(*body.clone(), shell_state);
840
841 if shell_state.is_returning() {
843 return exit_code;
844 }
845
846 if shell_state.exit_requested {
848 return shell_state.exit_code;
849 }
850 }
851
852 exit_code
853 }
854 Ast::FunctionDefinition { name, body } => {
855 shell_state.define_function(name.clone(), *body);
857 0
858 }
859 Ast::FunctionCall { name, args } => {
860 if let Some(function_body) = shell_state.get_function(&name).cloned() {
861 if shell_state.function_depth >= shell_state.max_recursion_depth {
863 eprintln!(
864 "Function recursion limit ({}) exceeded",
865 shell_state.max_recursion_depth
866 );
867 return 1;
868 }
869
870 shell_state.enter_function();
872
873 let old_positional = shell_state.positional_params.clone();
875
876 shell_state.set_positional_params(args.clone());
878
879 let exit_code = execute(function_body, shell_state);
881
882 if shell_state.is_returning() {
884 let return_value = shell_state.get_return_value().unwrap_or(0);
885
886 shell_state.set_positional_params(old_positional);
888
889 shell_state.exit_function();
891
892 shell_state.clear_return();
894
895 return return_value;
897 }
898
899 shell_state.set_positional_params(old_positional);
901
902 shell_state.exit_function();
904
905 exit_code
906 } else {
907 eprintln!("Function '{}' not found", name);
908 1
909 }
910 }
911 Ast::Return { value } => {
912 if shell_state.function_depth == 0 {
914 eprintln!("Return statement outside of function");
915 return 1;
916 }
917
918 let exit_code = if let Some(ref val) = value {
920 val.parse::<i32>().unwrap_or(0)
921 } else {
922 0
923 };
924
925 shell_state.set_return(exit_code);
927
928 exit_code
930 }
931 Ast::And { left, right } => {
932 let left_exit = execute(*left, shell_state);
934
935 if shell_state.is_returning() {
937 return left_exit;
938 }
939
940 if left_exit == 0 {
942 execute(*right, shell_state)
943 } else {
944 left_exit
945 }
946 }
947 Ast::Or { left, right } => {
948 let left_exit = execute(*left, shell_state);
950
951 if shell_state.is_returning() {
953 return left_exit;
954 }
955
956 if left_exit != 0 {
958 execute(*right, shell_state)
959 } else {
960 left_exit
961 }
962 }
963 }
964}
965
966fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
967 if cmd.args.is_empty() {
968 return 0;
969 }
970
971 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
973 let expanded_args = match expand_wildcards(&var_expanded_args) {
974 Ok(args) => args,
975 Err(_) => return 1,
976 };
977
978 if expanded_args.is_empty() {
979 return 0;
980 }
981
982 if shell_state.get_function(&expanded_args[0]).is_some() {
984 let function_call = Ast::FunctionCall {
986 name: expanded_args[0].clone(),
987 args: expanded_args[1..].to_vec(),
988 };
989 return execute(function_call, shell_state);
990 }
991
992 if crate::builtins::is_builtin(&expanded_args[0]) {
993 let temp_cmd = ShellCommand {
995 args: expanded_args,
996 input: cmd.input.clone(),
997 output: cmd.output.clone(),
998 append: cmd.append.clone(),
999 here_doc_delimiter: None,
1000 here_doc_quoted: false,
1001 here_string_content: None,
1002 };
1003
1004 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
1006 struct CaptureWriter {
1008 buffer: Rc<RefCell<Vec<u8>>>,
1009 }
1010 impl std::io::Write for CaptureWriter {
1011 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1012 self.buffer.borrow_mut().extend_from_slice(buf);
1013 Ok(buf.len())
1014 }
1015 fn flush(&mut self) -> std::io::Result<()> {
1016 Ok(())
1017 }
1018 }
1019 let writer = CaptureWriter {
1020 buffer: capture_buffer.clone(),
1021 };
1022 crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
1023 } else {
1024 crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
1025 }
1026 } else {
1027 let mut env_assignments = Vec::new();
1030 let mut command_start_idx = 0;
1031
1032 for (idx, arg) in expanded_args.iter().enumerate() {
1033 if let Some(eq_pos) = arg.find('=')
1035 && eq_pos > 0
1036 {
1037 let var_part = &arg[..eq_pos];
1038 if var_part
1040 .chars()
1041 .next()
1042 .map(|c| c.is_alphabetic() || c == '_')
1043 .unwrap_or(false)
1044 && var_part.chars().all(|c| c.is_alphanumeric() || c == '_')
1045 {
1046 env_assignments.push(arg.clone());
1047 command_start_idx = idx + 1;
1048 continue;
1049 }
1050 }
1051 break;
1053 }
1054
1055 let has_command = command_start_idx < expanded_args.len();
1057
1058 if !has_command {
1061 for assignment in &env_assignments {
1062 if let Some(eq_pos) = assignment.find('=') {
1063 let var_name = &assignment[..eq_pos];
1064 let var_value = &assignment[eq_pos + 1..];
1065 shell_state.set_var(var_name, var_value.to_string());
1066 }
1067 }
1068 }
1069
1070 let mut command = if has_command {
1072 let mut cmd = Command::new(&expanded_args[command_start_idx]);
1073 cmd.args(&expanded_args[command_start_idx + 1..]);
1074
1075 let mut child_env = shell_state.get_env_for_child();
1077
1078 for assignment in env_assignments {
1080 if let Some(eq_pos) = assignment.find('=') {
1081 let var_name = assignment[..eq_pos].to_string();
1082 let var_value = assignment[eq_pos + 1..].to_string();
1083 child_env.insert(var_name, var_value);
1084 }
1085 }
1086
1087 cmd.env_clear();
1088 for (key, value) in child_env {
1089 cmd.env(key, value);
1090 }
1091
1092 let capturing = shell_state.capture_output.is_some();
1094 if capturing {
1095 cmd.stdout(Stdio::piped());
1096 }
1097
1098 Some(cmd)
1099 } else {
1100 None
1101 };
1102
1103 if let Some(ref input_file) = cmd.input {
1105 let expanded_input = expand_variables_in_string(input_file, shell_state);
1106 if let Some(ref mut command) = command {
1107 match File::open(&expanded_input) {
1108 Ok(file) => {
1109 command.stdin(Stdio::from(file));
1110 }
1111 Err(e) => {
1112 if shell_state.colors_enabled {
1113 eprintln!(
1114 "{}Error opening input file '{}{}",
1115 shell_state.color_scheme.error,
1116 input_file,
1117 &format!("': {}\x1b[0m", e)
1118 );
1119 } else {
1120 eprintln!("Error opening input file '{}': {}", input_file, e);
1121 }
1122 return 1;
1123 }
1124 }
1125 } else {
1126 match File::open(&expanded_input) {
1128 Ok(_) => {
1129 }
1131 Err(e) => {
1132 if shell_state.colors_enabled {
1133 eprintln!(
1134 "{}Error opening input file '{}{}",
1135 shell_state.color_scheme.error,
1136 input_file,
1137 &format!("': {}\x1b[0m", e)
1138 );
1139 } else {
1140 eprintln!("Error opening input file '{}': {}", input_file, e);
1141 }
1142 return 1;
1143 }
1144 }
1145 }
1146 } else if let Some(ref delimiter) = cmd.here_doc_delimiter {
1147 let here_doc_content = collect_here_document_content(delimiter, shell_state);
1149 let expanded_content = if cmd.here_doc_quoted {
1152 here_doc_content.clone() } else {
1154 expand_variables_in_string(&here_doc_content, shell_state)
1155 };
1156
1157 if let Some(ref mut command) = command {
1158 let pipe_result = pipe();
1159 match pipe_result {
1160 Ok((reader, mut writer)) => {
1161 use std::io::Write;
1162 if let Err(e) = writeln!(writer, "{}", expanded_content) {
1163 if shell_state.colors_enabled {
1164 eprintln!(
1165 "{}Error writing here-document content: {}\x1b[0m",
1166 shell_state.color_scheme.error, e
1167 );
1168 } else {
1169 eprintln!("Error writing here-document content: {}", e);
1170 }
1171 return 1;
1172 }
1173 command.stdin(Stdio::from(reader));
1175 }
1176 Err(e) => {
1177 if shell_state.colors_enabled {
1178 eprintln!(
1179 "{}Error creating pipe for here-document: {}\x1b[0m",
1180 shell_state.color_scheme.error, e
1181 );
1182 } else {
1183 eprintln!("Error creating pipe for here-document: {}", e);
1184 }
1185 return 1;
1186 }
1187 }
1188 }
1189 } else if let Some(ref content) = cmd.here_string_content {
1191 let expanded_content = expand_variables_in_string(content, shell_state);
1193
1194 if let Some(ref mut command) = command {
1195 let pipe_result = pipe();
1196 match pipe_result {
1197 Ok((reader, mut writer)) => {
1198 use std::io::Write;
1199 if let Err(e) = write!(writer, "{}", expanded_content) {
1200 if shell_state.colors_enabled {
1201 eprintln!(
1202 "{}Error writing here-string content: {}\x1b[0m",
1203 shell_state.color_scheme.error, e
1204 );
1205 } else {
1206 eprintln!("Error writing here-string content: {}", e);
1207 }
1208 return 1;
1209 }
1210 command.stdin(Stdio::from(reader));
1212 }
1213 Err(e) => {
1214 if shell_state.colors_enabled {
1215 eprintln!(
1216 "{}Error creating pipe for here-string: {}\x1b[0m",
1217 shell_state.color_scheme.error, e
1218 );
1219 } else {
1220 eprintln!("Error creating pipe for here-string: {}", e);
1221 }
1222 return 1;
1223 }
1224 }
1225 }
1226 }
1228
1229 if let Some(ref output_file) = cmd.output {
1231 let expanded_output = expand_variables_in_string(output_file, shell_state);
1232 match File::create(&expanded_output) {
1233 Ok(file) => {
1234 if let Some(ref mut command) = command {
1235 command.stdout(Stdio::from(file));
1236 }
1237 }
1239 Err(e) => {
1240 if shell_state.colors_enabled {
1241 eprintln!(
1242 "{}Error creating output file '{}{}",
1243 shell_state.color_scheme.error,
1244 output_file,
1245 &format!("': {}\x1b[0m", e)
1246 );
1247 } else {
1248 eprintln!("Error creating output file '{}': {}", output_file, e);
1249 }
1250 return 1;
1251 }
1252 }
1253 } else if let Some(ref append_file) = cmd.append {
1254 let expanded_append = expand_variables_in_string(append_file, shell_state);
1255 match File::options()
1256 .append(true)
1257 .create(true)
1258 .open(&expanded_append)
1259 {
1260 Ok(file) => {
1261 if let Some(ref mut command) = command {
1262 command.stdout(Stdio::from(file));
1263 }
1264 }
1266 Err(e) => {
1267 if shell_state.colors_enabled {
1268 eprintln!(
1269 "{}Error opening append file '{}{}",
1270 shell_state.color_scheme.error,
1271 append_file,
1272 &format!("': {}\x1b[0m", e)
1273 );
1274 } else {
1275 eprintln!("Error opening append file '{}': {}", append_file, e);
1276 }
1277 return 1;
1278 }
1279 }
1280 }
1281
1282 let Some(mut command) = command else {
1284 return 0;
1285 };
1286
1287 let capturing = shell_state.capture_output.is_some();
1289
1290 match command.spawn() {
1291 Ok(mut child) => {
1292 if capturing && let Some(mut stdout) = child.stdout.take() {
1294 use std::io::Read;
1295 let mut output = Vec::new();
1296 if stdout.read_to_end(&mut output).is_ok()
1297 && let Some(ref capture_buffer) = shell_state.capture_output
1298 {
1299 capture_buffer.borrow_mut().extend_from_slice(&output);
1300 }
1301 }
1302
1303 match child.wait() {
1304 Ok(status) => status.code().unwrap_or(0),
1305 Err(e) => {
1306 if shell_state.colors_enabled {
1307 eprintln!(
1308 "{}Error waiting for command: {}\x1b[0m",
1309 shell_state.color_scheme.error, e
1310 );
1311 } else {
1312 eprintln!("Error waiting for command: {}", e);
1313 }
1314 1
1315 }
1316 }
1317 }
1318 Err(e) => {
1319 if shell_state.colors_enabled {
1320 eprintln!(
1321 "{}Command spawn error: {}\x1b[0m",
1322 shell_state.color_scheme.error, e
1323 );
1324 } else {
1325 eprintln!("Command spawn error: {}", e);
1326 }
1327 1
1328 }
1329 }
1330 }
1331}
1332
1333fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
1334 let mut exit_code = 0;
1335 let mut previous_stdout = None;
1336
1337 for (i, cmd) in commands.iter().enumerate() {
1338 if cmd.args.is_empty() {
1339 continue;
1340 }
1341
1342 let is_last = i == commands.len() - 1;
1343
1344 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1346 let expanded_args = match expand_wildcards(&var_expanded_args) {
1347 Ok(args) => args,
1348 Err(_) => return 1,
1349 };
1350
1351 if expanded_args.is_empty() {
1352 continue;
1353 }
1354
1355 if crate::builtins::is_builtin(&expanded_args[0]) {
1356 let temp_cmd = ShellCommand {
1359 args: expanded_args,
1360 input: cmd.input.clone(),
1361 output: cmd.output.clone(),
1362 append: cmd.append.clone(),
1363 here_doc_delimiter: None,
1364 here_doc_quoted: false,
1365 here_string_content: None,
1366 };
1367 if !is_last {
1368 let (reader, writer) = match pipe() {
1370 Ok(p) => p,
1371 Err(e) => {
1372 if shell_state.colors_enabled {
1373 eprintln!(
1374 "{}Error creating pipe for builtin: {}\x1b[0m",
1375 shell_state.color_scheme.error, e
1376 );
1377 } else {
1378 eprintln!("Error creating pipe for builtin: {}", e);
1379 }
1380 return 1;
1381 }
1382 };
1383 exit_code = crate::builtins::execute_builtin(
1385 &temp_cmd,
1386 shell_state,
1387 Some(Box::new(writer)),
1388 );
1389 previous_stdout = Some(Stdio::from(reader));
1391 } else {
1392 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
1394 struct CaptureWriter {
1396 buffer: Rc<RefCell<Vec<u8>>>,
1397 }
1398 impl std::io::Write for CaptureWriter {
1399 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1400 self.buffer.borrow_mut().extend_from_slice(buf);
1401 Ok(buf.len())
1402 }
1403 fn flush(&mut self) -> std::io::Result<()> {
1404 Ok(())
1405 }
1406 }
1407 let writer = CaptureWriter {
1408 buffer: capture_buffer.clone(),
1409 };
1410 exit_code = crate::builtins::execute_builtin(
1411 &temp_cmd,
1412 shell_state,
1413 Some(Box::new(writer)),
1414 );
1415 } else {
1416 exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
1418 }
1419 previous_stdout = None;
1420 }
1421 } else {
1422 let mut command = Command::new(&expanded_args[0]);
1423 command.args(&expanded_args[1..]);
1424
1425 let child_env = shell_state.get_env_for_child();
1427 command.env_clear();
1428 for (key, value) in child_env {
1429 command.env(key, value);
1430 }
1431
1432 if let Some(prev) = previous_stdout.take() {
1434 command.stdin(prev);
1435 }
1436
1437 if !is_last {
1439 command.stdout(Stdio::piped());
1440 } else if shell_state.capture_output.is_some() {
1441 command.stdout(Stdio::piped());
1443 }
1444
1445 if i == 0 {
1447 if let Some(ref input_file) = cmd.input {
1448 let expanded_input = expand_variables_in_string(input_file, shell_state);
1449 match File::open(&expanded_input) {
1450 Ok(file) => {
1451 command.stdin(Stdio::from(file));
1452 }
1453 Err(e) => {
1454 if shell_state.colors_enabled {
1455 eprintln!(
1456 "{}Error opening input file '{}{}",
1457 shell_state.color_scheme.error,
1458 input_file,
1459 &format!("': {}\x1b[0m", e)
1460 );
1461 } else {
1462 eprintln!("Error opening input file '{}': {}", input_file, e);
1463 }
1464 return 1;
1465 }
1466 }
1467 } else if let Some(ref delimiter) = cmd.here_doc_delimiter {
1468 let here_doc_content = collect_here_document_content(delimiter, shell_state);
1470 let expanded_content = if cmd.here_doc_quoted {
1473 here_doc_content } else {
1475 expand_variables_in_string(&here_doc_content, shell_state)
1476 };
1477 let pipe_result = pipe();
1478 match pipe_result {
1479 Ok((reader, mut writer)) => {
1480 use std::io::Write;
1481 if let Err(e) = writeln!(writer, "{}", expanded_content) {
1482 if shell_state.colors_enabled {
1483 eprintln!(
1484 "{}Error writing here-document content: {}\x1b[0m",
1485 shell_state.color_scheme.error, e
1486 );
1487 } else {
1488 eprintln!("Error writing here-document content: {}", e);
1489 }
1490 return 1;
1491 }
1492 command.stdin(Stdio::from(reader));
1493 }
1494 Err(e) => {
1495 if shell_state.colors_enabled {
1496 eprintln!(
1497 "{}Error creating pipe for here-document: {}\x1b[0m",
1498 shell_state.color_scheme.error, e
1499 );
1500 } else {
1501 eprintln!("Error creating pipe for here-document: {}", e);
1502 }
1503 return 1;
1504 }
1505 }
1506 } else if let Some(ref content) = cmd.here_string_content {
1507 let expanded_content = expand_variables_in_string(content, shell_state);
1509 let pipe_result = pipe();
1510 match pipe_result {
1511 Ok((reader, mut writer)) => {
1512 use std::io::Write;
1513 if let Err(e) = write!(writer, "{}", expanded_content) {
1514 if shell_state.colors_enabled {
1515 eprintln!(
1516 "{}Error writing here-string content: {}\x1b[0m",
1517 shell_state.color_scheme.error, e
1518 );
1519 } else {
1520 eprintln!("Error writing here-string content: {}", e);
1521 }
1522 return 1;
1523 }
1524 command.stdin(Stdio::from(reader));
1525 }
1526 Err(e) => {
1527 if shell_state.colors_enabled {
1528 eprintln!(
1529 "{}Error creating pipe for here-string: {}\x1b[0m",
1530 shell_state.color_scheme.error, e
1531 );
1532 } else {
1533 eprintln!("Error creating pipe for here-string: {}", e);
1534 }
1535 return 1;
1536 }
1537 }
1538 }
1539 }
1540
1541 if is_last {
1543 if let Some(ref output_file) = cmd.output {
1544 let expanded_output = expand_variables_in_string(output_file, shell_state);
1545 match File::create(&expanded_output) {
1546 Ok(file) => {
1547 command.stdout(Stdio::from(file));
1548 }
1549 Err(e) => {
1550 if shell_state.colors_enabled {
1551 eprintln!(
1552 "{}Error creating output file '{}{}",
1553 shell_state.color_scheme.error,
1554 output_file,
1555 &format!("': {}\x1b[0m", e)
1556 );
1557 } else {
1558 eprintln!("Error creating output file '{}': {}", output_file, e);
1559 }
1560 return 1;
1561 }
1562 }
1563 } else if let Some(ref append_file) = cmd.append {
1564 let expanded_append = expand_variables_in_string(append_file, shell_state);
1565 match File::options()
1566 .append(true)
1567 .create(true)
1568 .open(&expanded_append)
1569 {
1570 Ok(file) => {
1571 command.stdout(Stdio::from(file));
1572 }
1573 Err(e) => {
1574 if shell_state.colors_enabled {
1575 eprintln!(
1576 "{}Error opening append file '{}{}",
1577 shell_state.color_scheme.error,
1578 append_file,
1579 &format!("': {}\x1b[0m", e)
1580 );
1581 } else {
1582 eprintln!("Error opening append file '{}': {}", append_file, e);
1583 }
1584 return 1;
1585 }
1586 }
1587 }
1588 }
1589
1590 match command.spawn() {
1591 Ok(mut child) => {
1592 if !is_last {
1593 previous_stdout = child.stdout.take().map(Stdio::from);
1594 } else if shell_state.capture_output.is_some() {
1595 if let Some(mut stdout) = child.stdout.take() {
1597 use std::io::Read;
1598 let mut output = Vec::new();
1599 if stdout.read_to_end(&mut output).is_ok()
1600 && let Some(ref capture_buffer) = shell_state.capture_output
1601 {
1602 capture_buffer.borrow_mut().extend_from_slice(&output);
1603 }
1604 }
1605 }
1606 match child.wait() {
1607 Ok(status) => {
1608 exit_code = status.code().unwrap_or(0);
1609 }
1610 Err(e) => {
1611 if shell_state.colors_enabled {
1612 eprintln!(
1613 "{}Error waiting for command: {}\x1b[0m",
1614 shell_state.color_scheme.error, e
1615 );
1616 } else {
1617 eprintln!("Error waiting for command: {}", e);
1618 }
1619 exit_code = 1;
1620 }
1621 }
1622 }
1623 Err(e) => {
1624 if shell_state.colors_enabled {
1625 eprintln!(
1626 "{}Error spawning command '{}{}",
1627 shell_state.color_scheme.error,
1628 expanded_args[0],
1629 &format!("': {}\x1b[0m", e)
1630 );
1631 } else {
1632 eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1633 }
1634 exit_code = 1;
1635 }
1636 }
1637 }
1638 }
1639
1640 exit_code
1641}
1642
1643#[cfg(test)]
1644mod tests {
1645 use super::*;
1646
1647 #[test]
1648 fn test_execute_single_command_builtin() {
1649 let cmd = ShellCommand {
1650 args: vec!["true".to_string()],
1651 input: None,
1652 output: None,
1653 append: None,
1654 here_doc_delimiter: None,
1655 here_doc_quoted: false,
1656 here_string_content: None,
1657 };
1658 let mut shell_state = ShellState::new();
1659 let exit_code = execute_single_command(&cmd, &mut shell_state);
1660 assert_eq!(exit_code, 0);
1661 }
1662
1663 #[test]
1665 fn test_execute_single_command_external() {
1666 let cmd = ShellCommand {
1667 args: vec!["true".to_string()], input: None,
1669 output: None,
1670 append: None,
1671 here_doc_delimiter: None,
1672 here_doc_quoted: false,
1673 here_string_content: None,
1674 };
1675 let mut shell_state = ShellState::new();
1676 let exit_code = execute_single_command(&cmd, &mut shell_state);
1677 assert_eq!(exit_code, 0);
1678 }
1679
1680 #[test]
1681 fn test_execute_single_command_external_nonexistent() {
1682 let cmd = ShellCommand {
1683 args: vec!["nonexistent_command".to_string()],
1684 input: None,
1685 output: None,
1686 append: None,
1687 here_doc_delimiter: None,
1688 here_doc_quoted: false,
1689 here_string_content: None,
1690 };
1691 let mut shell_state = ShellState::new();
1692 let exit_code = execute_single_command(&cmd, &mut shell_state);
1693 assert_eq!(exit_code, 1); }
1695
1696 #[test]
1697 fn test_execute_pipeline() {
1698 let commands = vec![
1699 ShellCommand {
1700 args: vec!["printf".to_string(), "hello".to_string()],
1701 input: None,
1702 output: None,
1703 append: None,
1704 here_doc_delimiter: None,
1705 here_doc_quoted: false,
1706 here_string_content: None,
1707 },
1708 ShellCommand {
1709 args: vec!["cat".to_string()], input: None,
1711 output: None,
1712 append: None,
1713 here_doc_delimiter: None,
1714 here_doc_quoted: false,
1715 here_string_content: None,
1716 },
1717 ];
1718 let mut shell_state = ShellState::new();
1719 let exit_code = execute_pipeline(&commands, &mut shell_state);
1720 assert_eq!(exit_code, 0);
1721 }
1722
1723 #[test]
1724 fn test_execute_empty_pipeline() {
1725 let commands = vec![];
1726 let mut shell_state = ShellState::new();
1727 let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
1728 assert_eq!(exit_code, 0);
1729 }
1730
1731 #[test]
1732 fn test_execute_single_command() {
1733 let ast = Ast::Pipeline(vec![ShellCommand {
1734 args: vec!["true".to_string()],
1735 input: None,
1736 output: None,
1737 append: None,
1738 here_doc_delimiter: None,
1739 here_doc_quoted: false,
1740 here_string_content: None,
1741 }]);
1742 let mut shell_state = ShellState::new();
1743 let exit_code = execute(ast, &mut shell_state);
1744 assert_eq!(exit_code, 0);
1745 }
1746
1747 #[test]
1748 fn test_execute_function_definition() {
1749 let ast = Ast::FunctionDefinition {
1750 name: "test_func".to_string(),
1751 body: Box::new(Ast::Pipeline(vec![ShellCommand {
1752 args: vec!["echo".to_string(), "hello".to_string()],
1753 input: None,
1754 output: None,
1755 append: None,
1756 here_doc_delimiter: None,
1757 here_doc_quoted: false,
1758 here_string_content: None,
1759 }])),
1760 };
1761 let mut shell_state = ShellState::new();
1762 let exit_code = execute(ast, &mut shell_state);
1763 assert_eq!(exit_code, 0);
1764
1765 assert!(shell_state.get_function("test_func").is_some());
1767 }
1768
1769 #[test]
1770 fn test_execute_function_call() {
1771 let mut shell_state = ShellState::new();
1773 shell_state.define_function(
1774 "test_func".to_string(),
1775 Ast::Pipeline(vec![ShellCommand {
1776 args: vec!["echo".to_string(), "hello".to_string()],
1777 input: None,
1778 output: None,
1779 append: None,
1780 here_doc_delimiter: None,
1781 here_doc_quoted: false,
1782 here_string_content: None,
1783 }]),
1784 );
1785
1786 let ast = Ast::FunctionCall {
1788 name: "test_func".to_string(),
1789 args: vec![],
1790 };
1791 let exit_code = execute(ast, &mut shell_state);
1792 assert_eq!(exit_code, 0);
1793 }
1794
1795 #[test]
1796 fn test_execute_function_call_with_args() {
1797 let mut shell_state = ShellState::new();
1799 shell_state.define_function(
1800 "test_func".to_string(),
1801 Ast::Pipeline(vec![ShellCommand {
1802 args: vec!["echo".to_string(), "arg1".to_string()],
1803 input: None,
1804 output: None,
1805 append: None,
1806 here_doc_delimiter: None,
1807 here_doc_quoted: false,
1808 here_string_content: None,
1809 }]),
1810 );
1811
1812 let ast = Ast::FunctionCall {
1814 name: "test_func".to_string(),
1815 args: vec!["hello".to_string()],
1816 };
1817 let exit_code = execute(ast, &mut shell_state);
1818 assert_eq!(exit_code, 0);
1819 }
1820
1821 #[test]
1822 fn test_execute_nonexistent_function() {
1823 let mut shell_state = ShellState::new();
1824 let ast = Ast::FunctionCall {
1825 name: "nonexistent".to_string(),
1826 args: vec![],
1827 };
1828 let exit_code = execute(ast, &mut shell_state);
1829 assert_eq!(exit_code, 1); }
1831
1832 #[test]
1833 fn test_execute_function_integration() {
1834 let mut shell_state = ShellState::new();
1836
1837 let define_ast = Ast::FunctionDefinition {
1839 name: "hello".to_string(),
1840 body: Box::new(Ast::Pipeline(vec![ShellCommand {
1841 args: vec!["printf".to_string(), "Hello from function".to_string()],
1842 input: None,
1843 output: None,
1844 append: None,
1845 here_doc_delimiter: None,
1846 here_doc_quoted: false,
1847 here_string_content: None,
1848 }])),
1849 };
1850 let exit_code = execute(define_ast, &mut shell_state);
1851 assert_eq!(exit_code, 0);
1852
1853 let call_ast = Ast::FunctionCall {
1855 name: "hello".to_string(),
1856 args: vec![],
1857 };
1858 let exit_code = execute(call_ast, &mut shell_state);
1859 assert_eq!(exit_code, 0);
1860 }
1861
1862 #[test]
1863 fn test_execute_function_with_local_variables() {
1864 let mut shell_state = ShellState::new();
1865
1866 shell_state.set_var("global_var", "global_value".to_string());
1868
1869 let define_ast = Ast::FunctionDefinition {
1871 name: "test_func".to_string(),
1872 body: Box::new(Ast::Sequence(vec![
1873 Ast::LocalAssignment {
1874 var: "local_var".to_string(),
1875 value: "local_value".to_string(),
1876 },
1877 Ast::Assignment {
1878 var: "global_var".to_string(),
1879 value: "modified_in_function".to_string(),
1880 },
1881 Ast::Pipeline(vec![ShellCommand {
1882 args: vec!["printf".to_string(), "success".to_string()],
1883 input: None,
1884 output: None,
1885 append: None,
1886 here_doc_delimiter: None,
1887 here_doc_quoted: false,
1888 here_string_content: None,
1889 }]),
1890 ])),
1891 };
1892 let exit_code = execute(define_ast, &mut shell_state);
1893 assert_eq!(exit_code, 0);
1894
1895 assert_eq!(
1897 shell_state.get_var("global_var"),
1898 Some("global_value".to_string())
1899 );
1900
1901 let call_ast = Ast::FunctionCall {
1903 name: "test_func".to_string(),
1904 args: vec![],
1905 };
1906 let exit_code = execute(call_ast, &mut shell_state);
1907 assert_eq!(exit_code, 0);
1908
1909 assert_eq!(
1911 shell_state.get_var("global_var"),
1912 Some("modified_in_function".to_string())
1913 );
1914 }
1915
1916 #[test]
1917 fn test_execute_nested_function_calls() {
1918 let mut shell_state = ShellState::new();
1919
1920 shell_state.set_var("global_var", "global".to_string());
1922
1923 let outer_func = Ast::FunctionDefinition {
1925 name: "outer".to_string(),
1926 body: Box::new(Ast::Sequence(vec![
1927 Ast::Assignment {
1928 var: "global_var".to_string(),
1929 value: "outer_modified".to_string(),
1930 },
1931 Ast::FunctionCall {
1932 name: "inner".to_string(),
1933 args: vec![],
1934 },
1935 Ast::Pipeline(vec![ShellCommand {
1936 args: vec!["printf".to_string(), "outer_done".to_string()],
1937 input: None,
1938 output: None,
1939 append: None,
1940 here_doc_delimiter: None,
1941 here_doc_quoted: false,
1942 here_string_content: None,
1943 }]),
1944 ])),
1945 };
1946
1947 let inner_func = Ast::FunctionDefinition {
1949 name: "inner".to_string(),
1950 body: Box::new(Ast::Sequence(vec![
1951 Ast::Assignment {
1952 var: "global_var".to_string(),
1953 value: "inner_modified".to_string(),
1954 },
1955 Ast::Pipeline(vec![ShellCommand {
1956 args: vec!["printf".to_string(), "inner_done".to_string()],
1957 input: None,
1958 output: None,
1959 append: None,
1960 here_doc_delimiter: None,
1961 here_doc_quoted: false,
1962 here_string_content: None,
1963 }]),
1964 ])),
1965 };
1966
1967 execute(outer_func, &mut shell_state);
1969 execute(inner_func, &mut shell_state);
1970
1971 shell_state.set_var("global_var", "initial".to_string());
1973
1974 let call_ast = Ast::FunctionCall {
1976 name: "outer".to_string(),
1977 args: vec![],
1978 };
1979 let exit_code = execute(call_ast, &mut shell_state);
1980 assert_eq!(exit_code, 0);
1981
1982 assert_eq!(
1985 shell_state.get_var("global_var"),
1986 Some("inner_modified".to_string())
1987 );
1988 }
1989
1990 #[test]
1991 fn test_here_string_execution() {
1992 let cmd = ShellCommand {
1994 args: vec!["cat".to_string()],
1995 input: None,
1996 output: None,
1997 append: None,
1998 here_doc_delimiter: None,
1999 here_doc_quoted: false,
2000 here_string_content: Some("hello world".to_string()),
2001 };
2002
2003 assert_eq!(cmd.args, vec!["cat"]);
2006 assert_eq!(cmd.here_string_content, Some("hello world".to_string()));
2007 }
2008
2009 #[test]
2010 fn test_here_document_execution() {
2011 let cmd = ShellCommand {
2013 args: vec!["cat".to_string()],
2014 input: None,
2015 output: None,
2016 append: None,
2017 here_doc_delimiter: Some("EOF".to_string()),
2018 here_doc_quoted: false,
2019 here_string_content: None,
2020 };
2021
2022 assert_eq!(cmd.args, vec!["cat"]);
2025 assert_eq!(cmd.here_doc_delimiter, Some("EOF".to_string()));
2026 }
2027
2028 #[test]
2029 fn test_here_document_with_variable_expansion() {
2030 let mut shell_state = ShellState::new();
2032 shell_state.set_var("PWD", "/test/path".to_string());
2033
2034 let content = "Working dir: $PWD";
2036 let expanded = expand_variables_in_string(content, &mut shell_state);
2037
2038 assert_eq!(expanded, "Working dir: /test/path");
2039 }
2040
2041 #[test]
2042 fn test_here_document_with_command_substitution_builtin() {
2043 let mut shell_state = ShellState::new();
2045 shell_state.set_var("PWD", "/test/dir".to_string());
2046
2047 let content = "Current directory: `pwd`";
2049 let expanded = expand_variables_in_string(content, &mut shell_state);
2050
2051 assert!(expanded.contains("Current directory: "));
2053 }
2054}