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