1use std::fs::File;
2use std::io::pipe;
3use std::process::{Command, Stdio};
4use std::rc::Rc;
5use std::cell::RefCell;
6
7use super::parser::{Ast, ShellCommand};
8use super::state::ShellState;
9
10fn execute_and_capture_output(ast: Ast, shell_state: &mut ShellState) -> Result<String, String> {
13 let (reader, writer) = pipe().map_err(|e| format!("Failed to create pipe: {}", e))?;
15
16 match &ast {
21 Ast::Pipeline(commands) if commands.len() == 1 => {
22 let cmd = &commands[0];
23 if cmd.args.is_empty() {
24 return Ok(String::new());
25 }
26
27 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
29 let expanded_args = expand_wildcards(&var_expanded_args)
30 .map_err(|e| format!("Wildcard expansion failed: {}", e))?;
31
32 if expanded_args.is_empty() {
33 return Ok(String::new());
34 }
35
36 if shell_state.get_function(&expanded_args[0]).is_some() {
38 let previous_capture = shell_state.capture_output.clone();
40
41 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
43 shell_state.capture_output = Some(capture_buffer.clone());
44
45 let function_call_ast = Ast::FunctionCall {
47 name: expanded_args[0].clone(),
48 args: expanded_args[1..].to_vec(),
49 };
50
51 let exit_code = execute(function_call_ast, shell_state);
52
53 let captured = capture_buffer.borrow().clone();
55 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
56
57 shell_state.capture_output = previous_capture;
59
60 if exit_code == 0 {
61 Ok(output)
62 } else {
63 Err(format!("Function failed with exit code {}", exit_code))
64 }
65 } else if crate::builtins::is_builtin(&expanded_args[0]) {
66 let temp_cmd = ShellCommand {
67 args: expanded_args,
68 input: cmd.input.clone(),
69 output: None, append: None,
71 };
72
73 let exit_code = crate::builtins::execute_builtin(
75 &temp_cmd,
76 shell_state,
77 Some(Box::new(writer)),
78 );
79
80 drop(temp_cmd); let mut output = String::new();
83 use std::io::Read;
84 let mut reader = reader;
85 reader.read_to_string(&mut output)
86 .map_err(|e| format!("Failed to read output: {}", e))?;
87
88 if exit_code == 0 {
89 Ok(output.trim_end().to_string())
90 } else {
91 Err(format!("Command failed with exit code {}", exit_code))
92 }
93 } else {
94 drop(writer); let mut command = Command::new(&expanded_args[0]);
98 command.args(&expanded_args[1..]);
99 command.stdout(Stdio::piped());
100 command.stderr(Stdio::null()); let child_env = shell_state.get_env_for_child();
104 command.env_clear();
105 for (key, value) in child_env {
106 command.env(key, value);
107 }
108
109 let output = command.output()
110 .map_err(|e| format!("Failed to execute command: {}", e))?;
111
112 if output.status.success() {
113 Ok(String::from_utf8_lossy(&output.stdout).trim_end().to_string())
114 } else {
115 Err(format!("Command failed with exit code {}", output.status.code().unwrap_or(1)))
116 }
117 }
118 }
119 _ => {
120 drop(writer);
124
125 Err("Complex command substitutions not yet fully supported".to_string())
128 }
129 }
130}
131
132fn expand_variables_in_args(args: &[String], shell_state: &mut ShellState) -> Vec<String> {
133 let mut expanded_args = Vec::new();
134
135 for arg in args {
136 let expanded_arg = expand_variables_in_string(arg, shell_state);
138 expanded_args.push(expanded_arg);
139 }
140
141 expanded_args
142}
143
144pub fn expand_variables_in_string(input: &str, shell_state: &mut ShellState) -> String {
145 let mut result = String::new();
146 let mut chars = input.chars().peekable();
147
148 while let Some(ch) = chars.next() {
149 if ch == '$' {
150 if let Some(&'(') = chars.peek() {
152 chars.next(); if let Some(&'(') = chars.peek() {
156 chars.next(); let mut arithmetic_expr = String::new();
159 let mut paren_depth = 1;
160 let mut found_closing = false;
161
162 while let Some(c) = chars.next() {
163 if c == '(' {
164 paren_depth += 1;
165 arithmetic_expr.push(c);
166 } else if c == ')' {
167 paren_depth -= 1;
168 if paren_depth == 0 {
169 if let Some(&')') = chars.peek() {
171 chars.next(); found_closing = true;
173 break;
174 } else {
175 result.push_str("$((");
177 result.push_str(&arithmetic_expr);
178 result.push(')');
179 break;
180 }
181 }
182 arithmetic_expr.push(c);
183 } else {
184 arithmetic_expr.push(c);
185 }
186 }
187
188 if found_closing {
189 let mut expanded_expr = String::new();
193 let mut expr_chars = arithmetic_expr.chars().peekable();
194
195 while let Some(ch) = expr_chars.next() {
196 if ch == '$' {
197 let mut var_name = String::new();
199 if let Some(&c) = expr_chars.peek() {
200 if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' || c.is_ascii_digit() {
201 var_name.push(c);
202 expr_chars.next();
203 } else {
204 while let Some(&c) = expr_chars.peek() {
205 if c.is_alphanumeric() || c == '_' {
206 var_name.push(c);
207 expr_chars.next();
208 } else {
209 break;
210 }
211 }
212 }
213 }
214
215 if !var_name.is_empty() {
216 if let Some(value) = shell_state.get_var(&var_name) {
217 expanded_expr.push_str(&value);
218 } else {
219 expanded_expr.push('0');
221 }
222 } else {
223 expanded_expr.push('$');
224 }
225 } else {
226 expanded_expr.push(ch);
227 }
228 }
229
230 match crate::arithmetic::evaluate_arithmetic_expression(&expanded_expr, shell_state) {
231 Ok(value) => {
232 result.push_str(&value.to_string());
233 }
234 Err(e) => {
235 if shell_state.colors_enabled {
237 result.push_str(&format!("{}arithmetic error: {}{}",
238 shell_state.color_scheme.error, e, "\x1b[0m"));
239 } else {
240 result.push_str(&format!("arithmetic error: {}", e));
241 }
242 }
243 }
244 } else {
245 result.push_str("$((");
247 result.push_str(&arithmetic_expr);
248 }
250 continue;
251 }
252
253 let mut sub_command = String::new();
255 let mut paren_depth = 1;
256
257 for c in chars.by_ref() {
258 if c == '(' {
259 paren_depth += 1;
260 sub_command.push(c);
261 } else if c == ')' {
262 paren_depth -= 1;
263 if paren_depth == 0 {
264 break;
265 }
266 sub_command.push(c);
267 } else {
268 sub_command.push(c);
269 }
270 }
271
272 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
275 let expanded_tokens = match crate::lexer::expand_aliases(
277 tokens,
278 shell_state,
279 &mut std::collections::HashSet::new()
280 ) {
281 Ok(t) => t,
282 Err(_) => {
283 result.push_str("$(");
285 result.push_str(&sub_command);
286 result.push(')');
287 continue;
288 }
289 };
290
291 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
292 match execute_and_capture_output(ast, shell_state) {
294 Ok(output) => {
295 result.push_str(&output);
296 }
297 Err(_) => {
298 result.push_str("$(");
300 result.push_str(&sub_command);
301 result.push(')');
302 }
303 }
304 } else {
305 let tokens_str = sub_command.trim();
307 if tokens_str.contains(' ') {
308 let parts: Vec<&str> = tokens_str.split_whitespace().collect();
310 if let Some(first_token) = parts.first()
311 && shell_state.get_function(first_token).is_some() {
312 let function_call = Ast::FunctionCall {
314 name: first_token.to_string(),
315 args: parts[1..].iter().map(|s| s.to_string()).collect(),
316 };
317 match execute_and_capture_output(function_call, shell_state) {
318 Ok(output) => {
319 result.push_str(&output);
320 continue;
321 }
322 Err(_) => {
323 }
325 }
326 }
327 }
328 result.push_str("$(");
330 result.push_str(&sub_command);
331 result.push(')');
332 }
333 } else {
334 result.push_str("$(");
336 result.push_str(&sub_command);
337 result.push(')');
338 }
339 } else {
340 let mut var_name = String::new();
342 let mut next_ch = chars.peek();
343
344 if let Some(&c) = next_ch {
346 if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' {
347 var_name.push(c);
348 chars.next(); } else if c.is_ascii_digit() {
350 var_name.push(c);
352 chars.next();
353 } else {
354 while let Some(&c) = next_ch {
356 if c.is_alphanumeric() || c == '_' {
357 var_name.push(c);
358 chars.next(); next_ch = chars.peek();
360 } else {
361 break;
362 }
363 }
364 }
365 }
366
367 if !var_name.is_empty() {
368 if let Some(value) = shell_state.get_var(&var_name) {
369 result.push_str(&value);
370 } else {
371 if var_name.chars().next().unwrap().is_ascii_digit() ||
374 var_name == "?" || var_name == "$" || var_name == "0" ||
375 var_name == "#" || var_name == "*" || var_name == "@" {
376 } else {
378 result.push('$');
380 result.push_str(&var_name);
381 }
382 }
383 } else {
384 result.push('$');
385 }
386 }
387 } else if ch == '`' {
388 let mut sub_command = String::new();
390
391 for c in chars.by_ref() {
392 if c == '`' {
393 break;
394 }
395 sub_command.push(c);
396 }
397
398 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
400 let expanded_tokens = match crate::lexer::expand_aliases(
402 tokens,
403 shell_state,
404 &mut std::collections::HashSet::new()
405 ) {
406 Ok(t) => t,
407 Err(_) => {
408 result.push('`');
410 result.push_str(&sub_command);
411 result.push('`');
412 continue;
413 }
414 };
415
416 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
417 match execute_and_capture_output(ast, shell_state) {
419 Ok(output) => {
420 result.push_str(&output);
421 }
422 Err(_) => {
423 result.push('`');
425 result.push_str(&sub_command);
426 result.push('`');
427 }
428 }
429 } else {
430 result.push('`');
432 result.push_str(&sub_command);
433 result.push('`');
434 }
435 } else {
436 result.push('`');
438 result.push_str(&sub_command);
439 result.push('`');
440 }
441 } else {
442 result.push(ch);
443 }
444 }
445
446 result
447}
448
449fn expand_wildcards(args: &[String]) -> Result<Vec<String>, String> {
450 let mut expanded_args = Vec::new();
451
452 for arg in args {
453 if arg.contains('*') || arg.contains('?') || arg.contains('[') {
454 match glob::glob(arg) {
456 Ok(paths) => {
457 let mut matches: Vec<String> = paths
458 .filter_map(|p| p.ok())
459 .map(|p| p.to_string_lossy().to_string())
460 .collect();
461 if matches.is_empty() {
462 expanded_args.push(arg.clone());
464 } else {
465 matches.sort();
467 expanded_args.extend(matches);
468 }
469 }
470 Err(_e) => {
471 expanded_args.push(arg.clone());
473 }
474 }
475 } else {
476 expanded_args.push(arg.clone());
477 }
478 }
479 Ok(expanded_args)
480}
481
482pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
483 match ast {
484 Ast::Assignment { var, value } => {
485 let expanded_value = expand_variables_in_string(&value, shell_state);
487 shell_state.set_var(&var, expanded_value);
488 0
489 }
490 Ast::LocalAssignment { var, value } => {
491 let expanded_value = expand_variables_in_string(&value, shell_state);
493 shell_state.set_local_var(&var, expanded_value);
494 0
495 }
496 Ast::Pipeline(commands) => {
497 if commands.is_empty() {
498 return 0;
499 }
500
501 if commands.len() == 1 {
502 execute_single_command(&commands[0], shell_state)
504 } else {
505 execute_pipeline(&commands, shell_state)
507 }
508 }
509 Ast::Sequence(asts) => {
510 let mut exit_code = 0;
511 for ast in asts {
512 exit_code = execute(ast, shell_state);
513
514 if shell_state.is_returning() {
516 return exit_code;
517 }
518 }
519 exit_code
520 }
521 Ast::If {
522 branches,
523 else_branch,
524 } => {
525 for (condition, then_branch) in branches {
526 let cond_exit = execute(*condition, shell_state);
527 if cond_exit == 0 {
528 let exit_code = execute(*then_branch, shell_state);
529
530 if shell_state.is_returning() {
532 return exit_code;
533 }
534
535 return exit_code;
536 }
537 }
538 if let Some(else_b) = else_branch {
539 let exit_code = execute(*else_b, shell_state);
540
541 if shell_state.is_returning() {
543 return exit_code;
544 }
545
546 exit_code
547 } else {
548 0
549 }
550 }
551 Ast::Case {
552 word,
553 cases,
554 default,
555 } => {
556 for (patterns, branch) in cases {
557 for pattern in &patterns {
558 if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
559 if glob_pattern.matches(&word) {
560 let exit_code = execute(branch, shell_state);
561
562 if shell_state.is_returning() {
564 return exit_code;
565 }
566
567 return exit_code;
568 }
569 } else {
570 if &word == pattern {
572 let exit_code = execute(branch, shell_state);
573
574 if shell_state.is_returning() {
576 return exit_code;
577 }
578
579 return exit_code;
580 }
581 }
582 }
583 }
584 if let Some(def) = default {
585 let exit_code = execute(*def, shell_state);
586
587 if shell_state.is_returning() {
589 return exit_code;
590 }
591
592 exit_code
593 } else {
594 0
595 }
596 }
597 Ast::For { variable, items, body } => {
598 let mut exit_code = 0;
599
600 for item in items {
602 shell_state.set_var(&variable, item.clone());
604
605 exit_code = execute(*body.clone(), shell_state);
607
608 if shell_state.is_returning() {
610 return exit_code;
611 }
612 }
613
614 exit_code
615 }
616 Ast::While { condition, body } => {
617 let mut exit_code = 0;
618
619 loop {
621 let cond_exit = execute(*condition.clone(), shell_state);
623
624 if shell_state.is_returning() {
626 return cond_exit;
627 }
628
629 if cond_exit != 0 {
631 break;
632 }
633
634 exit_code = execute(*body.clone(), shell_state);
636
637 if shell_state.is_returning() {
639 return exit_code;
640 }
641 }
642
643 exit_code
644 }
645 Ast::FunctionDefinition { name, body } => {
646 shell_state.define_function(name.clone(), *body);
648 0
649 }
650 Ast::FunctionCall { name, args } => {
651 if let Some(function_body) = shell_state.get_function(&name).cloned() {
652 if shell_state.function_depth >= shell_state.max_recursion_depth {
654 eprintln!("Function recursion limit ({}) exceeded", shell_state.max_recursion_depth);
655 return 1;
656 }
657
658 shell_state.enter_function();
660
661 let old_positional = shell_state.positional_params.clone();
663
664 shell_state.set_positional_params(args.clone());
666
667 let exit_code = execute(function_body, shell_state);
669
670 if shell_state.is_returning() {
672 let return_value = shell_state.get_return_value().unwrap_or(0);
673
674 shell_state.set_positional_params(old_positional);
676
677 shell_state.exit_function();
679
680 shell_state.clear_return();
682
683 return return_value;
685 }
686
687 shell_state.set_positional_params(old_positional);
689
690 shell_state.exit_function();
692
693 exit_code
694 } else {
695 eprintln!("Function '{}' not found", name);
696 1
697 }
698 }
699 Ast::Return { value } => {
700 if shell_state.function_depth == 0 {
702 eprintln!("Return statement outside of function");
703 return 1;
704 }
705
706 let exit_code = if let Some(ref val) = value {
708 val.parse::<i32>().unwrap_or(0)
709 } else {
710 0
711 };
712
713 shell_state.set_return(exit_code);
715
716 exit_code
718 }
719 Ast::And { left, right } => {
720 let left_exit = execute(*left, shell_state);
722
723 if shell_state.is_returning() {
725 return left_exit;
726 }
727
728 if left_exit == 0 {
730 execute(*right, shell_state)
731 } else {
732 left_exit
733 }
734 }
735 Ast::Or { left, right } => {
736 let left_exit = execute(*left, shell_state);
738
739 if shell_state.is_returning() {
741 return left_exit;
742 }
743
744 if left_exit != 0 {
746 execute(*right, shell_state)
747 } else {
748 left_exit
749 }
750 }
751 }
752}
753
754fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
755 if cmd.args.is_empty() {
756 return 0;
757 }
758
759 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
761 let expanded_args = match expand_wildcards(&var_expanded_args) {
762 Ok(args) => args,
763 Err(_) => return 1,
764 };
765
766 if expanded_args.is_empty() {
767 return 0;
768 }
769
770 if shell_state.get_function(&expanded_args[0]).is_some() {
772 let function_call = Ast::FunctionCall {
774 name: expanded_args[0].clone(),
775 args: expanded_args[1..].to_vec(),
776 };
777 return execute(function_call, shell_state);
778 }
779
780 if crate::builtins::is_builtin(&expanded_args[0]) {
781 let temp_cmd = ShellCommand {
783 args: expanded_args,
784 input: cmd.input.clone(),
785 output: cmd.output.clone(),
786 append: cmd.append.clone(),
787 };
788
789 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
791 struct CaptureWriter {
793 buffer: Rc<RefCell<Vec<u8>>>,
794 }
795 impl std::io::Write for CaptureWriter {
796 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
797 self.buffer.borrow_mut().extend_from_slice(buf);
798 Ok(buf.len())
799 }
800 fn flush(&mut self) -> std::io::Result<()> {
801 Ok(())
802 }
803 }
804 let writer = CaptureWriter { buffer: capture_buffer.clone() };
805 crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
806 } else {
807 crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
808 }
809 } else {
810 let mut command = Command::new(&expanded_args[0]);
811 command.args(&expanded_args[1..]);
812
813 let child_env = shell_state.get_env_for_child();
815 command.env_clear();
816 for (key, value) in child_env {
817 command.env(key, value);
818 }
819
820 let capturing = shell_state.capture_output.is_some();
822 if capturing {
823 command.stdout(Stdio::piped());
824 }
825
826 if let Some(ref input_file) = cmd.input {
828 let expanded_input = expand_variables_in_string(input_file, shell_state);
829 match File::open(&expanded_input) {
830 Ok(file) => {
831 command.stdin(Stdio::from(file));
832 }
833 Err(e) => {
834 if shell_state.colors_enabled {
835 eprintln!(
836 "{}Error opening input file '{}{}",
837 shell_state.color_scheme.error,
838 input_file,
839 &format!("': {}\x1b[0m", e)
840 );
841 } else {
842 eprintln!("Error opening input file '{}': {}", input_file, e);
843 }
844 return 1;
845 }
846 }
847 }
848
849 if let Some(ref output_file) = cmd.output {
851 let expanded_output = expand_variables_in_string(output_file, shell_state);
852 match File::create(&expanded_output) {
853 Ok(file) => {
854 command.stdout(Stdio::from(file));
855 }
856 Err(e) => {
857 if shell_state.colors_enabled {
858 eprintln!(
859 "{}Error creating output file '{}{}",
860 shell_state.color_scheme.error,
861 output_file,
862 &format!("': {}\x1b[0m", e)
863 );
864 } else {
865 eprintln!("Error creating output file '{}': {}", output_file, e);
866 }
867 return 1;
868 }
869 }
870 } else if let Some(ref append_file) = cmd.append {
871 let expanded_append = expand_variables_in_string(append_file, shell_state);
872 match File::options().append(true).create(true).open(&expanded_append) {
873 Ok(file) => {
874 command.stdout(Stdio::from(file));
875 }
876 Err(e) => {
877 if shell_state.colors_enabled {
878 eprintln!(
879 "{}Error opening append file '{}{}",
880 shell_state.color_scheme.error,
881 append_file,
882 &format!("': {}\x1b[0m", e)
883 );
884 } else {
885 eprintln!("Error opening append file '{}': {}", append_file, e);
886 }
887 return 1;
888 }
889 }
890 }
891
892 match command.spawn() {
893 Ok(mut child) => {
894 if capturing
896 && let Some(mut stdout) = child.stdout.take() {
897 use std::io::Read;
898 let mut output = Vec::new();
899 if stdout.read_to_end(&mut output).is_ok()
900 && let Some(ref capture_buffer) = shell_state.capture_output {
901 capture_buffer.borrow_mut().extend_from_slice(&output);
902 }
903 }
904
905 match child.wait() {
906 Ok(status) => status.code().unwrap_or(0),
907 Err(e) => {
908 if shell_state.colors_enabled {
909 eprintln!(
910 "{}Error waiting for command: {}\x1b[0m",
911 shell_state.color_scheme.error,
912 e
913 );
914 } else {
915 eprintln!("Error waiting for command: {}", e);
916 }
917 1
918 }
919 }
920 }
921 Err(e) => {
922 if shell_state.colors_enabled {
923 eprintln!(
924 "{}Command spawn error: {}\x1b[0m",
925 shell_state.color_scheme.error, e
926 );
927 } else {
928 eprintln!("Command spawn error: {}", e);
929 }
930 1
931 }
932 }
933 }
934}
935
936fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
937 let mut exit_code = 0;
938 let mut previous_stdout = None;
939
940 for (i, cmd) in commands.iter().enumerate() {
941 if cmd.args.is_empty() {
942 continue;
943 }
944
945 let is_last = i == commands.len() - 1;
946
947 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
949 let expanded_args = match expand_wildcards(&var_expanded_args) {
950 Ok(args) => args,
951 Err(_) => return 1,
952 };
953
954 if expanded_args.is_empty() {
955 continue;
956 }
957
958 if crate::builtins::is_builtin(&expanded_args[0]) {
959 let temp_cmd = ShellCommand {
962 args: expanded_args,
963 input: cmd.input.clone(),
964 output: cmd.output.clone(),
965 append: cmd.append.clone(),
966 };
967 if !is_last {
968 let (reader, writer) = match pipe() {
970 Ok(p) => p,
971 Err(e) => {
972 if shell_state.colors_enabled {
973 eprintln!(
974 "{}Error creating pipe for builtin: {}\x1b[0m",
975 shell_state.color_scheme.error,
976 e
977 );
978 } else {
979 eprintln!("Error creating pipe for builtin: {}", e);
980 }
981 return 1;
982 }
983 };
984 exit_code = crate::builtins::execute_builtin(
986 &temp_cmd,
987 shell_state,
988 Some(Box::new(writer)),
989 );
990 previous_stdout = Some(Stdio::from(reader));
992 } else {
993 exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
995 previous_stdout = None;
996 }
997 } else {
998 let mut command = Command::new(&expanded_args[0]);
999 command.args(&expanded_args[1..]);
1000
1001 let child_env = shell_state.get_env_for_child();
1003 command.env_clear();
1004 for (key, value) in child_env {
1005 command.env(key, value);
1006 }
1007
1008 if let Some(prev) = previous_stdout.take() {
1010 command.stdin(prev);
1011 }
1012
1013 if !is_last {
1015 command.stdout(Stdio::piped());
1016 }
1017
1018 if i == 0
1020 && let Some(ref input_file) = cmd.input {
1021 let expanded_input = expand_variables_in_string(input_file, shell_state);
1022 match File::open(&expanded_input) {
1023 Ok(file) => {
1024 command.stdin(Stdio::from(file));
1025 }
1026 Err(e) => {
1027 if shell_state.colors_enabled {
1028 eprintln!(
1029 "{}Error opening input file '{}{}",
1030 shell_state.color_scheme.error,
1031 input_file,
1032 &format!("': {}\x1b[0m", e)
1033 );
1034 } else {
1035 eprintln!("Error opening input file '{}': {}", input_file, e);
1036 }
1037 return 1;
1038 }
1039 }
1040 }
1041
1042 if is_last {
1044 if let Some(ref output_file) = cmd.output {
1045 let expanded_output = expand_variables_in_string(output_file, shell_state);
1046 match File::create(&expanded_output) {
1047 Ok(file) => {
1048 command.stdout(Stdio::from(file));
1049 }
1050 Err(e) => {
1051 if shell_state.colors_enabled {
1052 eprintln!(
1053 "{}Error creating output file '{}{}",
1054 shell_state.color_scheme.error,
1055 output_file,
1056 &format!("': {}\x1b[0m", e)
1057 );
1058 } else {
1059 eprintln!("Error creating output file '{}': {}", output_file, e);
1060 }
1061 return 1;
1062 }
1063 }
1064 } else if let Some(ref append_file) = cmd.append {
1065 let expanded_append = expand_variables_in_string(append_file, shell_state);
1066 match File::options().append(true).create(true).open(&expanded_append) {
1067 Ok(file) => {
1068 command.stdout(Stdio::from(file));
1069 }
1070 Err(e) => {
1071 if shell_state.colors_enabled {
1072 eprintln!(
1073 "{}Error opening append file '{}{}",
1074 shell_state.color_scheme.error,
1075 append_file,
1076 &format!("': {}\x1b[0m", e)
1077 );
1078 } else {
1079 eprintln!("Error opening append file '{}': {}", append_file, e);
1080 }
1081 return 1;
1082 }
1083 }
1084 }
1085 }
1086
1087 match command.spawn() {
1088 Ok(mut child) => {
1089 if !is_last {
1090 previous_stdout = child.stdout.take().map(Stdio::from);
1091 }
1092 match child.wait() {
1093 Ok(status) => {
1094 exit_code = status.code().unwrap_or(0);
1095 }
1096 Err(e) => {
1097 if shell_state.colors_enabled {
1098 eprintln!(
1099 "{}Error waiting for command: {}\x1b[0m",
1100 shell_state.color_scheme.error,
1101 e
1102 );
1103 } else {
1104 eprintln!("Error waiting for command: {}", e);
1105 }
1106 exit_code = 1;
1107 }
1108 }
1109 }
1110 Err(e) => {
1111 if shell_state.colors_enabled {
1112 eprintln!(
1113 "{}Error spawning command '{}{}",
1114 shell_state.color_scheme.error,
1115 expanded_args[0],
1116 &format!("': {}\x1b[0m", e)
1117 );
1118 } else {
1119 eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1120 }
1121 exit_code = 1;
1122 }
1123 }
1124 }
1125 }
1126
1127 exit_code
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132 use super::*;
1133
1134 #[test]
1135 fn test_execute_single_command_builtin() {
1136 let cmd = ShellCommand {
1137 args: vec!["true".to_string()],
1138 input: None,
1139 output: None,
1140 append: None,
1141 };
1142 let mut shell_state = crate::state::ShellState::new();
1143 let exit_code = execute_single_command(&cmd, &mut shell_state);
1144 assert_eq!(exit_code, 0);
1145 }
1146
1147 #[test]
1149 fn test_execute_single_command_external() {
1150 let cmd = ShellCommand {
1151 args: vec!["true".to_string()], input: None,
1153 output: None,
1154 append: None,
1155 };
1156 let mut shell_state = crate::state::ShellState::new();
1157 let exit_code = execute_single_command(&cmd, &mut shell_state);
1158 assert_eq!(exit_code, 0);
1159 }
1160
1161 #[test]
1162 fn test_execute_single_command_external_nonexistent() {
1163 let cmd = ShellCommand {
1164 args: vec!["nonexistent_command".to_string()],
1165 input: None,
1166 output: None,
1167 append: None,
1168 };
1169 let mut shell_state = crate::state::ShellState::new();
1170 let exit_code = execute_single_command(&cmd, &mut shell_state);
1171 assert_eq!(exit_code, 1); }
1173
1174 #[test]
1175 fn test_execute_pipeline() {
1176 let commands = vec![
1177 ShellCommand {
1178 args: vec!["printf".to_string(), "hello".to_string()],
1179 input: None,
1180 output: None,
1181 append: None,
1182 },
1183 ShellCommand {
1184 args: vec!["cat".to_string()], input: None,
1186 output: None,
1187 append: None,
1188 },
1189 ];
1190 let mut shell_state = crate::state::ShellState::new();
1191 let exit_code = execute_pipeline(&commands, &mut shell_state);
1192 assert_eq!(exit_code, 0);
1193 }
1194
1195 #[test]
1196 fn test_execute_empty_pipeline() {
1197 let commands = vec![];
1198 let mut shell_state = crate::state::ShellState::new();
1199 let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
1200 assert_eq!(exit_code, 0);
1201 }
1202
1203 #[test]
1204 fn test_execute_single_command() {
1205 let ast = Ast::Pipeline(vec![ShellCommand {
1206 args: vec!["true".to_string()],
1207 input: None,
1208 output: None,
1209 append: None,
1210 }]);
1211 let mut shell_state = crate::state::ShellState::new();
1212 let exit_code = execute(ast, &mut shell_state);
1213 assert_eq!(exit_code, 0);
1214 }
1215
1216 #[test]
1217 fn test_execute_function_definition() {
1218 let ast = Ast::FunctionDefinition {
1219 name: "test_func".to_string(),
1220 body: Box::new(Ast::Pipeline(vec![ShellCommand {
1221 args: vec!["echo".to_string(), "hello".to_string()],
1222 input: None,
1223 output: None,
1224 append: None,
1225 }])),
1226 };
1227 let mut shell_state = crate::state::ShellState::new();
1228 let exit_code = execute(ast, &mut shell_state);
1229 assert_eq!(exit_code, 0);
1230
1231 assert!(shell_state.get_function("test_func").is_some());
1233 }
1234
1235 #[test]
1236 fn test_execute_function_call() {
1237 let mut shell_state = crate::state::ShellState::new();
1239 shell_state.define_function(
1240 "test_func".to_string(),
1241 Ast::Pipeline(vec![ShellCommand {
1242 args: vec!["echo".to_string(), "hello".to_string()],
1243 input: None,
1244 output: None,
1245 append: None,
1246 }]),
1247 );
1248
1249 let ast = Ast::FunctionCall {
1251 name: "test_func".to_string(),
1252 args: vec![],
1253 };
1254 let exit_code = execute(ast, &mut shell_state);
1255 assert_eq!(exit_code, 0);
1256 }
1257
1258 #[test]
1259 fn test_execute_function_call_with_args() {
1260 let mut shell_state = crate::state::ShellState::new();
1262 shell_state.define_function(
1263 "test_func".to_string(),
1264 Ast::Pipeline(vec![ShellCommand {
1265 args: vec!["echo".to_string(), "arg1".to_string()],
1266 input: None,
1267 output: None,
1268 append: None,
1269 }]),
1270 );
1271
1272 let ast = Ast::FunctionCall {
1274 name: "test_func".to_string(),
1275 args: vec!["hello".to_string()],
1276 };
1277 let exit_code = execute(ast, &mut shell_state);
1278 assert_eq!(exit_code, 0);
1279 }
1280
1281 #[test]
1282 fn test_execute_nonexistent_function() {
1283 let mut shell_state = crate::state::ShellState::new();
1284 let ast = Ast::FunctionCall {
1285 name: "nonexistent".to_string(),
1286 args: vec![],
1287 };
1288 let exit_code = execute(ast, &mut shell_state);
1289 assert_eq!(exit_code, 1); }
1291
1292 #[test]
1293 fn test_execute_function_integration() {
1294 let mut shell_state = crate::state::ShellState::new();
1296
1297 let define_ast = Ast::FunctionDefinition {
1299 name: "hello".to_string(),
1300 body: Box::new(Ast::Pipeline(vec![ShellCommand {
1301 args: vec!["printf".to_string(), "Hello from function".to_string()],
1302 input: None,
1303 output: None,
1304 append: None,
1305 }])),
1306 };
1307 let exit_code = execute(define_ast, &mut shell_state);
1308 assert_eq!(exit_code, 0);
1309
1310 let call_ast = Ast::FunctionCall {
1312 name: "hello".to_string(),
1313 args: vec![],
1314 };
1315 let exit_code = execute(call_ast, &mut shell_state);
1316 assert_eq!(exit_code, 0);
1317 }
1318
1319 #[test]
1320 fn test_execute_function_with_local_variables() {
1321 let mut shell_state = crate::state::ShellState::new();
1322
1323 shell_state.set_var("global_var", "global_value".to_string());
1325
1326 let define_ast = Ast::FunctionDefinition {
1328 name: "test_func".to_string(),
1329 body: Box::new(Ast::Sequence(vec![
1330 Ast::LocalAssignment {
1331 var: "local_var".to_string(),
1332 value: "local_value".to_string(),
1333 },
1334 Ast::Assignment {
1335 var: "global_var".to_string(),
1336 value: "modified_in_function".to_string(),
1337 },
1338 Ast::Pipeline(vec![ShellCommand {
1339 args: vec!["printf".to_string(), "success".to_string()],
1340 input: None,
1341 output: None,
1342 append: None,
1343 }]),
1344 ])),
1345 };
1346 let exit_code = execute(define_ast, &mut shell_state);
1347 assert_eq!(exit_code, 0);
1348
1349 assert_eq!(shell_state.get_var("global_var"), Some("global_value".to_string()));
1351
1352 let call_ast = Ast::FunctionCall {
1354 name: "test_func".to_string(),
1355 args: vec![],
1356 };
1357 let exit_code = execute(call_ast, &mut shell_state);
1358 assert_eq!(exit_code, 0);
1359
1360 assert_eq!(shell_state.get_var("global_var"), Some("modified_in_function".to_string()));
1362 }
1363
1364 #[test]
1365 fn test_execute_nested_function_calls() {
1366 let mut shell_state = crate::state::ShellState::new();
1367
1368 shell_state.set_var("global_var", "global".to_string());
1370
1371 let outer_func = Ast::FunctionDefinition {
1373 name: "outer".to_string(),
1374 body: Box::new(Ast::Sequence(vec![
1375 Ast::Assignment {
1376 var: "global_var".to_string(),
1377 value: "outer_modified".to_string(),
1378 },
1379 Ast::FunctionCall {
1380 name: "inner".to_string(),
1381 args: vec![],
1382 },
1383 Ast::Pipeline(vec![ShellCommand {
1384 args: vec!["printf".to_string(), "outer_done".to_string()],
1385 input: None,
1386 output: None,
1387 append: None,
1388 }]),
1389 ])),
1390 };
1391
1392 let inner_func = Ast::FunctionDefinition {
1394 name: "inner".to_string(),
1395 body: Box::new(Ast::Sequence(vec![
1396 Ast::Assignment {
1397 var: "global_var".to_string(),
1398 value: "inner_modified".to_string(),
1399 },
1400 Ast::Pipeline(vec![ShellCommand {
1401 args: vec!["printf".to_string(), "inner_done".to_string()],
1402 input: None,
1403 output: None,
1404 append: None,
1405 }]),
1406 ])),
1407 };
1408
1409 execute(outer_func, &mut shell_state);
1411 execute(inner_func, &mut shell_state);
1412
1413 shell_state.set_var("global_var", "initial".to_string());
1415
1416 let call_ast = Ast::FunctionCall {
1418 name: "outer".to_string(),
1419 args: vec![],
1420 };
1421 let exit_code = execute(call_ast, &mut shell_state);
1422 assert_eq!(exit_code, 0);
1423
1424 assert_eq!(shell_state.get_var("global_var"), Some("inner_modified".to_string()));
1427 }
1428}