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) 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 here_doc_delimiter: None,
72 here_string_content: None,
73 };
74
75 let exit_code = crate::builtins::execute_builtin(
77 &temp_cmd,
78 shell_state,
79 Some(Box::new(writer)),
80 );
81
82 drop(temp_cmd); let mut output = String::new();
85 use std::io::Read;
86 let mut reader = reader;
87 reader
88 .read_to_string(&mut output)
89 .map_err(|e| format!("Failed to read output: {}", e))?;
90
91 if exit_code == 0 {
92 Ok(output.trim_end().to_string())
93 } else {
94 Err(format!("Command failed with exit code {}", exit_code))
95 }
96 } else {
97 drop(writer); let mut command = Command::new(&expanded_args[0]);
101 command.args(&expanded_args[1..]);
102 command.stdout(Stdio::piped());
103 command.stderr(Stdio::null()); let child_env = shell_state.get_env_for_child();
107 command.env_clear();
108 for (key, value) in child_env {
109 command.env(key, value);
110 }
111
112 let output = command
113 .output()
114 .map_err(|e| format!("Failed to execute command: {}", e))?;
115
116 if output.status.success() {
117 Ok(String::from_utf8_lossy(&output.stdout)
118 .trim_end()
119 .to_string())
120 } else {
121 Err(format!(
122 "Command failed with exit code {}",
123 output.status.code().unwrap_or(1)
124 ))
125 }
126 }
127 }
128 _ => {
129 drop(writer);
133
134 Err("Complex command substitutions not yet fully supported".to_string())
137 }
138 }
139}
140
141fn expand_variables_in_args(args: &[String], shell_state: &mut ShellState) -> Vec<String> {
142 let mut expanded_args = Vec::new();
143
144 for arg in args {
145 let expanded_arg = expand_variables_in_string(arg, shell_state);
147 expanded_args.push(expanded_arg);
148 }
149
150 expanded_args
151}
152
153pub fn expand_variables_in_string(input: &str, shell_state: &mut ShellState) -> String {
154 let mut result = String::new();
155 let mut chars = input.chars().peekable();
156
157 while let Some(ch) = chars.next() {
158 if ch == '$' {
159 if let Some(&'(') = chars.peek() {
161 chars.next(); if let Some(&'(') = chars.peek() {
165 chars.next(); let mut arithmetic_expr = String::new();
168 let mut paren_depth = 1;
169 let mut found_closing = false;
170
171 while let Some(c) = chars.next() {
172 if c == '(' {
173 paren_depth += 1;
174 arithmetic_expr.push(c);
175 } else if c == ')' {
176 paren_depth -= 1;
177 if paren_depth == 0 {
178 if let Some(&')') = chars.peek() {
180 chars.next(); found_closing = true;
182 break;
183 } else {
184 result.push_str("$((");
186 result.push_str(&arithmetic_expr);
187 result.push(')');
188 break;
189 }
190 }
191 arithmetic_expr.push(c);
192 } else {
193 arithmetic_expr.push(c);
194 }
195 }
196
197 if found_closing {
198 let mut expanded_expr = String::new();
202 let mut expr_chars = arithmetic_expr.chars().peekable();
203
204 while let Some(ch) = expr_chars.next() {
205 if ch == '$' {
206 let mut var_name = String::new();
208 if let Some(&c) = expr_chars.peek() {
209 if c == '?'
210 || c == '$'
211 || c == '0'
212 || c == '#'
213 || c == '*'
214 || c == '@'
215 || c.is_ascii_digit()
216 {
217 var_name.push(c);
218 expr_chars.next();
219 } else {
220 while let Some(&c) = expr_chars.peek() {
221 if c.is_alphanumeric() || c == '_' {
222 var_name.push(c);
223 expr_chars.next();
224 } else {
225 break;
226 }
227 }
228 }
229 }
230
231 if !var_name.is_empty() {
232 if let Some(value) = shell_state.get_var(&var_name) {
233 expanded_expr.push_str(&value);
234 } else {
235 expanded_expr.push('0');
237 }
238 } else {
239 expanded_expr.push('$');
240 }
241 } else {
242 expanded_expr.push(ch);
243 }
244 }
245
246 match crate::arithmetic::evaluate_arithmetic_expression(
247 &expanded_expr,
248 shell_state,
249 ) {
250 Ok(value) => {
251 result.push_str(&value.to_string());
252 }
253 Err(e) => {
254 if shell_state.colors_enabled {
256 result.push_str(&format!(
257 "{}arithmetic error: {}{}",
258 shell_state.color_scheme.error, e, "\x1b[0m"
259 ));
260 } else {
261 result.push_str(&format!("arithmetic error: {}", e));
262 }
263 }
264 }
265 } else {
266 result.push_str("$((");
268 result.push_str(&arithmetic_expr);
269 }
271 continue;
272 }
273
274 let mut sub_command = String::new();
276 let mut paren_depth = 1;
277
278 for c in chars.by_ref() {
279 if c == '(' {
280 paren_depth += 1;
281 sub_command.push(c);
282 } else if c == ')' {
283 paren_depth -= 1;
284 if paren_depth == 0 {
285 break;
286 }
287 sub_command.push(c);
288 } else {
289 sub_command.push(c);
290 }
291 }
292
293 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
296 let expanded_tokens = match crate::lexer::expand_aliases(
298 tokens,
299 shell_state,
300 &mut std::collections::HashSet::new(),
301 ) {
302 Ok(t) => t,
303 Err(_) => {
304 result.push_str("$(");
306 result.push_str(&sub_command);
307 result.push(')');
308 continue;
309 }
310 };
311
312 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
313 match execute_and_capture_output(ast, shell_state) {
315 Ok(output) => {
316 result.push_str(&output);
317 }
318 Err(_) => {
319 result.push_str("$(");
321 result.push_str(&sub_command);
322 result.push(')');
323 }
324 }
325 } else {
326 let tokens_str = sub_command.trim();
328 if tokens_str.contains(' ') {
329 let parts: Vec<&str> = tokens_str.split_whitespace().collect();
331 if let Some(first_token) = parts.first()
332 && shell_state.get_function(first_token).is_some()
333 {
334 let function_call = Ast::FunctionCall {
336 name: first_token.to_string(),
337 args: parts[1..].iter().map(|s| s.to_string()).collect(),
338 };
339 match execute_and_capture_output(function_call, shell_state) {
340 Ok(output) => {
341 result.push_str(&output);
342 continue;
343 }
344 Err(_) => {
345 }
347 }
348 }
349 }
350 result.push_str("$(");
352 result.push_str(&sub_command);
353 result.push(')');
354 }
355 } else {
356 result.push_str("$(");
358 result.push_str(&sub_command);
359 result.push(')');
360 }
361 } else {
362 let mut var_name = String::new();
364 let mut next_ch = chars.peek();
365
366 if let Some(&c) = next_ch {
368 if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' {
369 var_name.push(c);
370 chars.next(); } else if c.is_ascii_digit() {
372 var_name.push(c);
374 chars.next();
375 } else {
376 while let Some(&c) = next_ch {
378 if c.is_alphanumeric() || c == '_' {
379 var_name.push(c);
380 chars.next(); next_ch = chars.peek();
382 } else {
383 break;
384 }
385 }
386 }
387 }
388
389 if !var_name.is_empty() {
390 if let Some(value) = shell_state.get_var(&var_name) {
391 result.push_str(&value);
392 } else {
393 if var_name.chars().next().unwrap().is_ascii_digit()
396 || var_name == "?"
397 || var_name == "$"
398 || var_name == "0"
399 || var_name == "#"
400 || var_name == "*"
401 || var_name == "@"
402 {
403 } else {
405 result.push('$');
407 result.push_str(&var_name);
408 }
409 }
410 } else {
411 result.push('$');
412 }
413 }
414 } else if ch == '`' {
415 let mut sub_command = String::new();
417
418 for c in chars.by_ref() {
419 if c == '`' {
420 break;
421 }
422 sub_command.push(c);
423 }
424
425 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
427 let expanded_tokens = match crate::lexer::expand_aliases(
429 tokens,
430 shell_state,
431 &mut std::collections::HashSet::new(),
432 ) {
433 Ok(t) => t,
434 Err(_) => {
435 result.push('`');
437 result.push_str(&sub_command);
438 result.push('`');
439 continue;
440 }
441 };
442
443 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
444 match execute_and_capture_output(ast, shell_state) {
446 Ok(output) => {
447 result.push_str(&output);
448 }
449 Err(_) => {
450 result.push('`');
452 result.push_str(&sub_command);
453 result.push('`');
454 }
455 }
456 } else {
457 result.push('`');
459 result.push_str(&sub_command);
460 result.push('`');
461 }
462 } else {
463 result.push('`');
465 result.push_str(&sub_command);
466 result.push('`');
467 }
468 } else {
469 result.push(ch);
470 }
471 }
472
473 result
474}
475
476fn expand_wildcards(args: &[String]) -> Result<Vec<String>, String> {
477 let mut expanded_args = Vec::new();
478
479 for arg in args {
480 if arg.contains('*') || arg.contains('?') || arg.contains('[') {
481 match glob::glob(arg) {
483 Ok(paths) => {
484 let mut matches: Vec<String> = paths
485 .filter_map(|p| p.ok())
486 .map(|p| p.to_string_lossy().to_string())
487 .collect();
488 if matches.is_empty() {
489 expanded_args.push(arg.clone());
491 } else {
492 matches.sort();
494 expanded_args.extend(matches);
495 }
496 }
497 Err(_e) => {
498 expanded_args.push(arg.clone());
500 }
501 }
502 } else {
503 expanded_args.push(arg.clone());
504 }
505 }
506 Ok(expanded_args)
507}
508
509fn collect_here_document_content(delimiter: &str, shell_state: &mut ShellState) -> String {
513 if let Some(content) = shell_state.pending_heredoc_content.take() {
515 return content;
516 }
517
518 let stdin = std::io::stdin();
520 let mut reader = BufReader::new(stdin.lock());
521 let mut content = String::new();
522 let mut line = String::new();
523
524 loop {
525 line.clear();
526 match reader.read_line(&mut line) {
527 Ok(0) => {
528 break;
530 }
531 Ok(_) => {
532 let line_content = line.trim_end();
534 if line_content == delimiter {
535 break;
537 } else {
538 content.push_str(&line);
540 }
541 }
542 Err(e) => {
543 if shell_state.colors_enabled {
544 eprintln!(
545 "{}Error reading here-document content: {}\x1b[0m",
546 shell_state.color_scheme.error, e
547 );
548 } else {
549 eprintln!("Error reading here-document content: {}", e);
550 }
551 break;
552 }
553 }
554 }
555
556 content
557}
558
559pub fn execute_trap_handler(trap_cmd: &str, shell_state: &mut ShellState) -> i32 {
562 let saved_exit_code = shell_state.last_exit_code;
564
565 let result = match crate::lexer::lex(trap_cmd, shell_state) {
571 Ok(tokens) => {
572 match crate::lexer::expand_aliases(
573 tokens,
574 shell_state,
575 &mut std::collections::HashSet::new(),
576 ) {
577 Ok(expanded_tokens) => {
578 match crate::parser::parse(expanded_tokens) {
579 Ok(ast) => execute(ast, shell_state),
580 Err(_) => {
581 saved_exit_code
583 }
584 }
585 }
586 Err(_) => {
587 saved_exit_code
589 }
590 }
591 }
592 Err(_) => {
593 saved_exit_code
595 }
596 };
597
598 shell_state.last_exit_code = saved_exit_code;
600
601 result
602}
603
604pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
605 match ast {
606 Ast::Assignment { var, value } => {
607 let expanded_value = expand_variables_in_string(&value, shell_state);
609 shell_state.set_var(&var, expanded_value);
610 0
611 }
612 Ast::LocalAssignment { var, value } => {
613 let expanded_value = expand_variables_in_string(&value, shell_state);
615 shell_state.set_local_var(&var, expanded_value);
616 0
617 }
618 Ast::Pipeline(commands) => {
619 if commands.is_empty() {
620 return 0;
621 }
622
623 if commands.len() == 1 {
624 execute_single_command(&commands[0], shell_state)
626 } else {
627 execute_pipeline(&commands, shell_state)
629 }
630 }
631 Ast::Sequence(asts) => {
632 let mut exit_code = 0;
633 for ast in asts {
634 exit_code = execute(ast, shell_state);
635
636 if shell_state.is_returning() {
638 return exit_code;
639 }
640
641 if shell_state.exit_requested {
643 return shell_state.exit_code;
644 }
645 }
646 exit_code
647 }
648 Ast::If {
649 branches,
650 else_branch,
651 } => {
652 for (condition, then_branch) in branches {
653 let cond_exit = execute(*condition, shell_state);
654 if cond_exit == 0 {
655 let exit_code = execute(*then_branch, shell_state);
656
657 if shell_state.is_returning() {
659 return exit_code;
660 }
661
662 return exit_code;
663 }
664 }
665 if let Some(else_b) = else_branch {
666 let exit_code = execute(*else_b, shell_state);
667
668 if shell_state.is_returning() {
670 return exit_code;
671 }
672
673 exit_code
674 } else {
675 0
676 }
677 }
678 Ast::Case {
679 word,
680 cases,
681 default,
682 } => {
683 for (patterns, branch) in cases {
684 for pattern in &patterns {
685 if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
686 if glob_pattern.matches(&word) {
687 let exit_code = execute(branch, shell_state);
688
689 if shell_state.is_returning() {
691 return exit_code;
692 }
693
694 return exit_code;
695 }
696 } else {
697 if &word == pattern {
699 let exit_code = execute(branch, shell_state);
700
701 if shell_state.is_returning() {
703 return exit_code;
704 }
705
706 return exit_code;
707 }
708 }
709 }
710 }
711 if let Some(def) = default {
712 let exit_code = execute(*def, shell_state);
713
714 if shell_state.is_returning() {
716 return exit_code;
717 }
718
719 exit_code
720 } else {
721 0
722 }
723 }
724 Ast::For {
725 variable,
726 items,
727 body,
728 } => {
729 let mut exit_code = 0;
730
731 for item in items {
733 crate::state::process_pending_signals(shell_state);
735
736 if shell_state.exit_requested {
738 return shell_state.exit_code;
739 }
740
741 shell_state.set_var(&variable, item.clone());
743
744 exit_code = execute(*body.clone(), shell_state);
746
747 if shell_state.is_returning() {
749 return exit_code;
750 }
751
752 if shell_state.exit_requested {
754 return shell_state.exit_code;
755 }
756 }
757
758 exit_code
759 }
760 Ast::While { condition, body } => {
761 let mut exit_code = 0;
762
763 loop {
765 let cond_exit = execute(*condition.clone(), shell_state);
767
768 if shell_state.is_returning() {
770 return cond_exit;
771 }
772
773 if shell_state.exit_requested {
775 return shell_state.exit_code;
776 }
777
778 if cond_exit != 0 {
780 break;
781 }
782
783 exit_code = execute(*body.clone(), shell_state);
785
786 if shell_state.is_returning() {
788 return exit_code;
789 }
790
791 if shell_state.exit_requested {
793 return shell_state.exit_code;
794 }
795 }
796
797 exit_code
798 }
799 Ast::FunctionDefinition { name, body } => {
800 shell_state.define_function(name.clone(), *body);
802 0
803 }
804 Ast::FunctionCall { name, args } => {
805 if let Some(function_body) = shell_state.get_function(&name).cloned() {
806 if shell_state.function_depth >= shell_state.max_recursion_depth {
808 eprintln!(
809 "Function recursion limit ({}) exceeded",
810 shell_state.max_recursion_depth
811 );
812 return 1;
813 }
814
815 shell_state.enter_function();
817
818 let old_positional = shell_state.positional_params.clone();
820
821 shell_state.set_positional_params(args.clone());
823
824 let exit_code = execute(function_body, shell_state);
826
827 if shell_state.is_returning() {
829 let return_value = shell_state.get_return_value().unwrap_or(0);
830
831 shell_state.set_positional_params(old_positional);
833
834 shell_state.exit_function();
836
837 shell_state.clear_return();
839
840 return return_value;
842 }
843
844 shell_state.set_positional_params(old_positional);
846
847 shell_state.exit_function();
849
850 exit_code
851 } else {
852 eprintln!("Function '{}' not found", name);
853 1
854 }
855 }
856 Ast::Return { value } => {
857 if shell_state.function_depth == 0 {
859 eprintln!("Return statement outside of function");
860 return 1;
861 }
862
863 let exit_code = if let Some(ref val) = value {
865 val.parse::<i32>().unwrap_or(0)
866 } else {
867 0
868 };
869
870 shell_state.set_return(exit_code);
872
873 exit_code
875 }
876 Ast::And { left, right } => {
877 let left_exit = execute(*left, shell_state);
879
880 if shell_state.is_returning() {
882 return left_exit;
883 }
884
885 if left_exit == 0 {
887 execute(*right, shell_state)
888 } else {
889 left_exit
890 }
891 }
892 Ast::Or { left, right } => {
893 let left_exit = execute(*left, shell_state);
895
896 if shell_state.is_returning() {
898 return left_exit;
899 }
900
901 if left_exit != 0 {
903 execute(*right, shell_state)
904 } else {
905 left_exit
906 }
907 }
908 }
909}
910
911fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
912 if cmd.args.is_empty() {
913 return 0;
914 }
915
916 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
918 let expanded_args = match expand_wildcards(&var_expanded_args) {
919 Ok(args) => args,
920 Err(_) => return 1,
921 };
922
923 if expanded_args.is_empty() {
924 return 0;
925 }
926
927 if shell_state.get_function(&expanded_args[0]).is_some() {
929 let function_call = Ast::FunctionCall {
931 name: expanded_args[0].clone(),
932 args: expanded_args[1..].to_vec(),
933 };
934 return execute(function_call, shell_state);
935 }
936
937 if crate::builtins::is_builtin(&expanded_args[0]) {
938 let temp_cmd = ShellCommand {
940 args: expanded_args,
941 input: cmd.input.clone(),
942 output: cmd.output.clone(),
943 append: cmd.append.clone(),
944 here_doc_delimiter: None,
945 here_string_content: None,
946 };
947
948 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
950 struct CaptureWriter {
952 buffer: Rc<RefCell<Vec<u8>>>,
953 }
954 impl std::io::Write for CaptureWriter {
955 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
956 self.buffer.borrow_mut().extend_from_slice(buf);
957 Ok(buf.len())
958 }
959 fn flush(&mut self) -> std::io::Result<()> {
960 Ok(())
961 }
962 }
963 let writer = CaptureWriter {
964 buffer: capture_buffer.clone(),
965 };
966 crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
967 } else {
968 crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
969 }
970 } else {
971 let mut command = Command::new(&expanded_args[0]);
972 command.args(&expanded_args[1..]);
973
974 let child_env = shell_state.get_env_for_child();
976 command.env_clear();
977 for (key, value) in child_env {
978 command.env(key, value);
979 }
980
981 let capturing = shell_state.capture_output.is_some();
983 if capturing {
984 command.stdout(Stdio::piped());
985 }
986
987 if let Some(ref input_file) = cmd.input {
989 let expanded_input = expand_variables_in_string(input_file, shell_state);
990 match File::open(&expanded_input) {
991 Ok(file) => {
992 command.stdin(Stdio::from(file));
993 }
994 Err(e) => {
995 if shell_state.colors_enabled {
996 eprintln!(
997 "{}Error opening input file '{}{}",
998 shell_state.color_scheme.error,
999 input_file,
1000 &format!("': {}\x1b[0m", e)
1001 );
1002 } else {
1003 eprintln!("Error opening input file '{}': {}", input_file, e);
1004 }
1005 return 1;
1006 }
1007 }
1008 } else if let Some(ref delimiter) = cmd.here_doc_delimiter {
1009 let here_doc_content = collect_here_document_content(delimiter, shell_state);
1011 let pipe_result = pipe();
1012 match pipe_result {
1013 Ok((reader, mut writer)) => {
1014 use std::io::Write;
1015 if let Err(e) = writeln!(writer, "{}", here_doc_content) {
1016 if shell_state.colors_enabled {
1017 eprintln!(
1018 "{}Error writing here-document content: {}\x1b[0m",
1019 shell_state.color_scheme.error, e
1020 );
1021 } else {
1022 eprintln!("Error writing here-document content: {}", e);
1023 }
1024 return 1;
1025 }
1026 command.stdin(Stdio::from(reader));
1028 }
1029 Err(e) => {
1030 if shell_state.colors_enabled {
1031 eprintln!(
1032 "{}Error creating pipe for here-document: {}\x1b[0m",
1033 shell_state.color_scheme.error, e
1034 );
1035 } else {
1036 eprintln!("Error creating pipe for here-document: {}", e);
1037 }
1038 return 1;
1039 }
1040 }
1041 } else if let Some(ref content) = cmd.here_string_content {
1042 let expanded_content = expand_variables_in_string(content, shell_state);
1044 let pipe_result = pipe();
1045 match pipe_result {
1046 Ok((reader, mut writer)) => {
1047 use std::io::Write;
1048 if let Err(e) = write!(writer, "{}", expanded_content) {
1049 if shell_state.colors_enabled {
1050 eprintln!(
1051 "{}Error writing here-string content: {}\x1b[0m",
1052 shell_state.color_scheme.error, e
1053 );
1054 } else {
1055 eprintln!("Error writing here-string content: {}", e);
1056 }
1057 return 1;
1058 }
1059 command.stdin(Stdio::from(reader));
1061 }
1062 Err(e) => {
1063 if shell_state.colors_enabled {
1064 eprintln!(
1065 "{}Error creating pipe for here-string: {}\x1b[0m",
1066 shell_state.color_scheme.error, e
1067 );
1068 } else {
1069 eprintln!("Error creating pipe for here-string: {}", e);
1070 }
1071 return 1;
1072 }
1073 }
1074 }
1075
1076 if let Some(ref output_file) = cmd.output {
1078 let expanded_output = expand_variables_in_string(output_file, shell_state);
1079 match File::create(&expanded_output) {
1080 Ok(file) => {
1081 command.stdout(Stdio::from(file));
1082 }
1083 Err(e) => {
1084 if shell_state.colors_enabled {
1085 eprintln!(
1086 "{}Error creating output file '{}{}",
1087 shell_state.color_scheme.error,
1088 output_file,
1089 &format!("': {}\x1b[0m", e)
1090 );
1091 } else {
1092 eprintln!("Error creating output file '{}': {}", output_file, e);
1093 }
1094 return 1;
1095 }
1096 }
1097 } else if let Some(ref append_file) = cmd.append {
1098 let expanded_append = expand_variables_in_string(append_file, shell_state);
1099 match File::options()
1100 .append(true)
1101 .create(true)
1102 .open(&expanded_append)
1103 {
1104 Ok(file) => {
1105 command.stdout(Stdio::from(file));
1106 }
1107 Err(e) => {
1108 if shell_state.colors_enabled {
1109 eprintln!(
1110 "{}Error opening append file '{}{}",
1111 shell_state.color_scheme.error,
1112 append_file,
1113 &format!("': {}\x1b[0m", e)
1114 );
1115 } else {
1116 eprintln!("Error opening append file '{}': {}", append_file, e);
1117 }
1118 return 1;
1119 }
1120 }
1121 }
1122
1123 match command.spawn() {
1124 Ok(mut child) => {
1125 if capturing && let Some(mut stdout) = child.stdout.take() {
1127 use std::io::Read;
1128 let mut output = Vec::new();
1129 if stdout.read_to_end(&mut output).is_ok()
1130 && let Some(ref capture_buffer) = shell_state.capture_output
1131 {
1132 capture_buffer.borrow_mut().extend_from_slice(&output);
1133 }
1134 }
1135
1136 match child.wait() {
1137 Ok(status) => status.code().unwrap_or(0),
1138 Err(e) => {
1139 if shell_state.colors_enabled {
1140 eprintln!(
1141 "{}Error waiting for command: {}\x1b[0m",
1142 shell_state.color_scheme.error, e
1143 );
1144 } else {
1145 eprintln!("Error waiting for command: {}", e);
1146 }
1147 1
1148 }
1149 }
1150 }
1151 Err(e) => {
1152 if shell_state.colors_enabled {
1153 eprintln!(
1154 "{}Command spawn error: {}\x1b[0m",
1155 shell_state.color_scheme.error, e
1156 );
1157 } else {
1158 eprintln!("Command spawn error: {}", e);
1159 }
1160 1
1161 }
1162 }
1163 }
1164}
1165
1166fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
1167 let mut exit_code = 0;
1168 let mut previous_stdout = None;
1169
1170 for (i, cmd) in commands.iter().enumerate() {
1171 if cmd.args.is_empty() {
1172 continue;
1173 }
1174
1175 let is_last = i == commands.len() - 1;
1176
1177 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1179 let expanded_args = match expand_wildcards(&var_expanded_args) {
1180 Ok(args) => args,
1181 Err(_) => return 1,
1182 };
1183
1184 if expanded_args.is_empty() {
1185 continue;
1186 }
1187
1188 if crate::builtins::is_builtin(&expanded_args[0]) {
1189 let temp_cmd = ShellCommand {
1192 args: expanded_args,
1193 input: cmd.input.clone(),
1194 output: cmd.output.clone(),
1195 append: cmd.append.clone(),
1196 here_doc_delimiter: None,
1197 here_string_content: None,
1198 };
1199 if !is_last {
1200 let (reader, writer) = match pipe() {
1202 Ok(p) => p,
1203 Err(e) => {
1204 if shell_state.colors_enabled {
1205 eprintln!(
1206 "{}Error creating pipe for builtin: {}\x1b[0m",
1207 shell_state.color_scheme.error, e
1208 );
1209 } else {
1210 eprintln!("Error creating pipe for builtin: {}", e);
1211 }
1212 return 1;
1213 }
1214 };
1215 exit_code = crate::builtins::execute_builtin(
1217 &temp_cmd,
1218 shell_state,
1219 Some(Box::new(writer)),
1220 );
1221 previous_stdout = Some(Stdio::from(reader));
1223 } else {
1224 exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
1226 previous_stdout = None;
1227 }
1228 } else {
1229 let mut command = Command::new(&expanded_args[0]);
1230 command.args(&expanded_args[1..]);
1231
1232 let child_env = shell_state.get_env_for_child();
1234 command.env_clear();
1235 for (key, value) in child_env {
1236 command.env(key, value);
1237 }
1238
1239 if let Some(prev) = previous_stdout.take() {
1241 command.stdin(prev);
1242 }
1243
1244 if !is_last {
1246 command.stdout(Stdio::piped());
1247 }
1248
1249 if i == 0 {
1251 if let Some(ref input_file) = cmd.input {
1252 let expanded_input = expand_variables_in_string(input_file, shell_state);
1253 match File::open(&expanded_input) {
1254 Ok(file) => {
1255 command.stdin(Stdio::from(file));
1256 }
1257 Err(e) => {
1258 if shell_state.colors_enabled {
1259 eprintln!(
1260 "{}Error opening input file '{}{}",
1261 shell_state.color_scheme.error,
1262 input_file,
1263 &format!("': {}\x1b[0m", e)
1264 );
1265 } else {
1266 eprintln!("Error opening input file '{}': {}", input_file, e);
1267 }
1268 return 1;
1269 }
1270 }
1271 } else if let Some(ref delimiter) = cmd.here_doc_delimiter {
1272 let here_doc_content = collect_here_document_content(delimiter, shell_state);
1274 let pipe_result = pipe();
1275 match pipe_result {
1276 Ok((reader, mut writer)) => {
1277 use std::io::Write;
1278 if let Err(e) = writeln!(writer, "{}", here_doc_content) {
1279 if shell_state.colors_enabled {
1280 eprintln!(
1281 "{}Error writing here-document content: {}\x1b[0m",
1282 shell_state.color_scheme.error, e
1283 );
1284 } else {
1285 eprintln!("Error writing here-document content: {}", e);
1286 }
1287 return 1;
1288 }
1289 command.stdin(Stdio::from(reader));
1290 }
1291 Err(e) => {
1292 if shell_state.colors_enabled {
1293 eprintln!(
1294 "{}Error creating pipe for here-document: {}\x1b[0m",
1295 shell_state.color_scheme.error, e
1296 );
1297 } else {
1298 eprintln!("Error creating pipe for here-document: {}", e);
1299 }
1300 return 1;
1301 }
1302 }
1303 } else if let Some(ref content) = cmd.here_string_content {
1304 let expanded_content = expand_variables_in_string(content, shell_state);
1306 let pipe_result = pipe();
1307 match pipe_result {
1308 Ok((reader, mut writer)) => {
1309 use std::io::Write;
1310 if let Err(e) = write!(writer, "{}", expanded_content) {
1311 if shell_state.colors_enabled {
1312 eprintln!(
1313 "{}Error writing here-string content: {}\x1b[0m",
1314 shell_state.color_scheme.error, e
1315 );
1316 } else {
1317 eprintln!("Error writing here-string content: {}", e);
1318 }
1319 return 1;
1320 }
1321 command.stdin(Stdio::from(reader));
1322 }
1323 Err(e) => {
1324 if shell_state.colors_enabled {
1325 eprintln!(
1326 "{}Error creating pipe for here-string: {}\x1b[0m",
1327 shell_state.color_scheme.error, e
1328 );
1329 } else {
1330 eprintln!("Error creating pipe for here-string: {}", e);
1331 }
1332 return 1;
1333 }
1334 }
1335 }
1336 }
1337
1338 if is_last {
1340 if let Some(ref output_file) = cmd.output {
1341 let expanded_output = expand_variables_in_string(output_file, shell_state);
1342 match File::create(&expanded_output) {
1343 Ok(file) => {
1344 command.stdout(Stdio::from(file));
1345 }
1346 Err(e) => {
1347 if shell_state.colors_enabled {
1348 eprintln!(
1349 "{}Error creating output file '{}{}",
1350 shell_state.color_scheme.error,
1351 output_file,
1352 &format!("': {}\x1b[0m", e)
1353 );
1354 } else {
1355 eprintln!("Error creating output file '{}': {}", output_file, e);
1356 }
1357 return 1;
1358 }
1359 }
1360 } else if let Some(ref append_file) = cmd.append {
1361 let expanded_append = expand_variables_in_string(append_file, shell_state);
1362 match File::options()
1363 .append(true)
1364 .create(true)
1365 .open(&expanded_append)
1366 {
1367 Ok(file) => {
1368 command.stdout(Stdio::from(file));
1369 }
1370 Err(e) => {
1371 if shell_state.colors_enabled {
1372 eprintln!(
1373 "{}Error opening append file '{}{}",
1374 shell_state.color_scheme.error,
1375 append_file,
1376 &format!("': {}\x1b[0m", e)
1377 );
1378 } else {
1379 eprintln!("Error opening append file '{}': {}", append_file, e);
1380 }
1381 return 1;
1382 }
1383 }
1384 }
1385 }
1386
1387 match command.spawn() {
1388 Ok(mut child) => {
1389 if !is_last {
1390 previous_stdout = child.stdout.take().map(Stdio::from);
1391 }
1392 match child.wait() {
1393 Ok(status) => {
1394 exit_code = status.code().unwrap_or(0);
1395 }
1396 Err(e) => {
1397 if shell_state.colors_enabled {
1398 eprintln!(
1399 "{}Error waiting for command: {}\x1b[0m",
1400 shell_state.color_scheme.error, e
1401 );
1402 } else {
1403 eprintln!("Error waiting for command: {}", e);
1404 }
1405 exit_code = 1;
1406 }
1407 }
1408 }
1409 Err(e) => {
1410 if shell_state.colors_enabled {
1411 eprintln!(
1412 "{}Error spawning command '{}{}",
1413 shell_state.color_scheme.error,
1414 expanded_args[0],
1415 &format!("': {}\x1b[0m", e)
1416 );
1417 } else {
1418 eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1419 }
1420 exit_code = 1;
1421 }
1422 }
1423 }
1424 }
1425
1426 exit_code
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431 use super::*;
1432
1433 #[test]
1434 fn test_execute_single_command_builtin() {
1435 let cmd = ShellCommand {
1436 args: vec!["true".to_string()],
1437 input: None,
1438 output: None,
1439 append: None,
1440 here_doc_delimiter: None,
1441 here_string_content: None,
1442 };
1443 let mut shell_state = ShellState::new();
1444 let exit_code = execute_single_command(&cmd, &mut shell_state);
1445 assert_eq!(exit_code, 0);
1446 }
1447
1448 #[test]
1450 fn test_execute_single_command_external() {
1451 let cmd = ShellCommand {
1452 args: vec!["true".to_string()], input: None,
1454 output: None,
1455 append: None,
1456 here_doc_delimiter: None,
1457 here_string_content: None,
1458 };
1459 let mut shell_state = ShellState::new();
1460 let exit_code = execute_single_command(&cmd, &mut shell_state);
1461 assert_eq!(exit_code, 0);
1462 }
1463
1464 #[test]
1465 fn test_execute_single_command_external_nonexistent() {
1466 let cmd = ShellCommand {
1467 args: vec!["nonexistent_command".to_string()],
1468 input: None,
1469 output: None,
1470 append: None,
1471 here_doc_delimiter: None,
1472 here_string_content: None,
1473 };
1474 let mut shell_state = ShellState::new();
1475 let exit_code = execute_single_command(&cmd, &mut shell_state);
1476 assert_eq!(exit_code, 1); }
1478
1479 #[test]
1480 fn test_execute_pipeline() {
1481 let commands = vec![
1482 ShellCommand {
1483 args: vec!["printf".to_string(), "hello".to_string()],
1484 input: None,
1485 output: None,
1486 append: None,
1487 here_doc_delimiter: None,
1488 here_string_content: None,
1489 },
1490 ShellCommand {
1491 args: vec!["cat".to_string()], input: None,
1493 output: None,
1494 append: None,
1495 here_doc_delimiter: None,
1496 here_string_content: None,
1497 },
1498 ];
1499 let mut shell_state = ShellState::new();
1500 let exit_code = execute_pipeline(&commands, &mut shell_state);
1501 assert_eq!(exit_code, 0);
1502 }
1503
1504 #[test]
1505 fn test_execute_empty_pipeline() {
1506 let commands = vec![];
1507 let mut shell_state = ShellState::new();
1508 let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
1509 assert_eq!(exit_code, 0);
1510 }
1511
1512 #[test]
1513 fn test_execute_single_command() {
1514 let ast = Ast::Pipeline(vec![ShellCommand {
1515 args: vec!["true".to_string()],
1516 input: None,
1517 output: None,
1518 append: None,
1519 here_doc_delimiter: None,
1520 here_string_content: None,
1521 }]);
1522 let mut shell_state = ShellState::new();
1523 let exit_code = execute(ast, &mut shell_state);
1524 assert_eq!(exit_code, 0);
1525 }
1526
1527 #[test]
1528 fn test_execute_function_definition() {
1529 let ast = Ast::FunctionDefinition {
1530 name: "test_func".to_string(),
1531 body: Box::new(Ast::Pipeline(vec![ShellCommand {
1532 args: vec!["echo".to_string(), "hello".to_string()],
1533 input: None,
1534 output: None,
1535 append: None,
1536 here_doc_delimiter: None,
1537 here_string_content: None,
1538 }])),
1539 };
1540 let mut shell_state = ShellState::new();
1541 let exit_code = execute(ast, &mut shell_state);
1542 assert_eq!(exit_code, 0);
1543
1544 assert!(shell_state.get_function("test_func").is_some());
1546 }
1547
1548 #[test]
1549 fn test_execute_function_call() {
1550 let mut shell_state = ShellState::new();
1552 shell_state.define_function(
1553 "test_func".to_string(),
1554 Ast::Pipeline(vec![ShellCommand {
1555 args: vec!["echo".to_string(), "hello".to_string()],
1556 input: None,
1557 output: None,
1558 append: None,
1559 here_doc_delimiter: None,
1560 here_string_content: None,
1561 }]),
1562 );
1563
1564 let ast = Ast::FunctionCall {
1566 name: "test_func".to_string(),
1567 args: vec![],
1568 };
1569 let exit_code = execute(ast, &mut shell_state);
1570 assert_eq!(exit_code, 0);
1571 }
1572
1573 #[test]
1574 fn test_execute_function_call_with_args() {
1575 let mut shell_state = ShellState::new();
1577 shell_state.define_function(
1578 "test_func".to_string(),
1579 Ast::Pipeline(vec![ShellCommand {
1580 args: vec!["echo".to_string(), "arg1".to_string()],
1581 input: None,
1582 output: None,
1583 append: None,
1584 here_doc_delimiter: None,
1585 here_string_content: None,
1586 }]),
1587 );
1588
1589 let ast = Ast::FunctionCall {
1591 name: "test_func".to_string(),
1592 args: vec!["hello".to_string()],
1593 };
1594 let exit_code = execute(ast, &mut shell_state);
1595 assert_eq!(exit_code, 0);
1596 }
1597
1598 #[test]
1599 fn test_execute_nonexistent_function() {
1600 let mut shell_state = ShellState::new();
1601 let ast = Ast::FunctionCall {
1602 name: "nonexistent".to_string(),
1603 args: vec![],
1604 };
1605 let exit_code = execute(ast, &mut shell_state);
1606 assert_eq!(exit_code, 1); }
1608
1609 #[test]
1610 fn test_execute_function_integration() {
1611 let mut shell_state = ShellState::new();
1613
1614 let define_ast = Ast::FunctionDefinition {
1616 name: "hello".to_string(),
1617 body: Box::new(Ast::Pipeline(vec![ShellCommand {
1618 args: vec!["printf".to_string(), "Hello from function".to_string()],
1619 input: None,
1620 output: None,
1621 append: None,
1622 here_doc_delimiter: None,
1623 here_string_content: None,
1624 }])),
1625 };
1626 let exit_code = execute(define_ast, &mut shell_state);
1627 assert_eq!(exit_code, 0);
1628
1629 let call_ast = Ast::FunctionCall {
1631 name: "hello".to_string(),
1632 args: vec![],
1633 };
1634 let exit_code = execute(call_ast, &mut shell_state);
1635 assert_eq!(exit_code, 0);
1636 }
1637
1638 #[test]
1639 fn test_execute_function_with_local_variables() {
1640 let mut shell_state = ShellState::new();
1641
1642 shell_state.set_var("global_var", "global_value".to_string());
1644
1645 let define_ast = Ast::FunctionDefinition {
1647 name: "test_func".to_string(),
1648 body: Box::new(Ast::Sequence(vec![
1649 Ast::LocalAssignment {
1650 var: "local_var".to_string(),
1651 value: "local_value".to_string(),
1652 },
1653 Ast::Assignment {
1654 var: "global_var".to_string(),
1655 value: "modified_in_function".to_string(),
1656 },
1657 Ast::Pipeline(vec![ShellCommand {
1658 args: vec!["printf".to_string(), "success".to_string()],
1659 input: None,
1660 output: None,
1661 append: None,
1662 here_doc_delimiter: None,
1663 here_string_content: None,
1664 }]),
1665 ])),
1666 };
1667 let exit_code = execute(define_ast, &mut shell_state);
1668 assert_eq!(exit_code, 0);
1669
1670 assert_eq!(
1672 shell_state.get_var("global_var"),
1673 Some("global_value".to_string())
1674 );
1675
1676 let call_ast = Ast::FunctionCall {
1678 name: "test_func".to_string(),
1679 args: vec![],
1680 };
1681 let exit_code = execute(call_ast, &mut shell_state);
1682 assert_eq!(exit_code, 0);
1683
1684 assert_eq!(
1686 shell_state.get_var("global_var"),
1687 Some("modified_in_function".to_string())
1688 );
1689 }
1690
1691 #[test]
1692 fn test_execute_nested_function_calls() {
1693 let mut shell_state = ShellState::new();
1694
1695 shell_state.set_var("global_var", "global".to_string());
1697
1698 let outer_func = Ast::FunctionDefinition {
1700 name: "outer".to_string(),
1701 body: Box::new(Ast::Sequence(vec![
1702 Ast::Assignment {
1703 var: "global_var".to_string(),
1704 value: "outer_modified".to_string(),
1705 },
1706 Ast::FunctionCall {
1707 name: "inner".to_string(),
1708 args: vec![],
1709 },
1710 Ast::Pipeline(vec![ShellCommand {
1711 args: vec!["printf".to_string(), "outer_done".to_string()],
1712 input: None,
1713 output: None,
1714 append: None,
1715 here_doc_delimiter: None,
1716 here_string_content: None,
1717 }]),
1718 ])),
1719 };
1720
1721 let inner_func = Ast::FunctionDefinition {
1723 name: "inner".to_string(),
1724 body: Box::new(Ast::Sequence(vec![
1725 Ast::Assignment {
1726 var: "global_var".to_string(),
1727 value: "inner_modified".to_string(),
1728 },
1729 Ast::Pipeline(vec![ShellCommand {
1730 args: vec!["printf".to_string(), "inner_done".to_string()],
1731 input: None,
1732 output: None,
1733 append: None,
1734 here_doc_delimiter: None,
1735 here_string_content: None,
1736 }]),
1737 ])),
1738 };
1739
1740 execute(outer_func, &mut shell_state);
1742 execute(inner_func, &mut shell_state);
1743
1744 shell_state.set_var("global_var", "initial".to_string());
1746
1747 let call_ast = Ast::FunctionCall {
1749 name: "outer".to_string(),
1750 args: vec![],
1751 };
1752 let exit_code = execute(call_ast, &mut shell_state);
1753 assert_eq!(exit_code, 0);
1754
1755 assert_eq!(
1758 shell_state.get_var("global_var"),
1759 Some("inner_modified".to_string())
1760 );
1761 }
1762
1763 #[test]
1764 fn test_here_string_execution() {
1765 let cmd = ShellCommand {
1767 args: vec!["cat".to_string()],
1768 input: None,
1769 output: None,
1770 append: None,
1771 here_doc_delimiter: None,
1772 here_string_content: Some("hello world".to_string()),
1773 };
1774
1775 assert_eq!(cmd.args, vec!["cat"]);
1778 assert_eq!(cmd.here_string_content, Some("hello world".to_string()));
1779 }
1780
1781 #[test]
1782 fn test_here_document_execution() {
1783 let cmd = ShellCommand {
1785 args: vec!["cat".to_string()],
1786 input: None,
1787 output: None,
1788 append: None,
1789 here_doc_delimiter: Some("EOF".to_string()),
1790 here_string_content: None,
1791 };
1792
1793 assert_eq!(cmd.args, vec!["cat"]);
1796 assert_eq!(cmd.here_doc_delimiter, Some("EOF".to_string()));
1797 }
1798}