1use super::*;
2
3pub enum Executor<'a> {
4 Command(&'a Interpreter<'a>),
5 Shebang(Shebang<'a>),
6}
7
8impl<'a> Executor<'a> {
9 pub fn command<'src>(
10 &self,
11 path: &Path,
12 recipe: &'src str,
13 working_directory: Option<&Path>,
14 ) -> RunResult<'src, Command> {
15 match self {
16 Self::Command(interpreter) => {
17 let mut command = Command::new(&interpreter.command.cooked);
18
19 if let Some(working_directory) = working_directory {
20 command.current_dir(working_directory);
21 }
22
23 for arg in &interpreter.arguments {
24 command.arg(&arg.cooked);
25 }
26
27 command.arg(path);
28
29 Ok(command)
30 }
31 Self::Shebang(shebang) => {
32 Platform::set_execute_permission(path).map_err(|error| Error::TempdirIo {
34 recipe,
35 io_error: error,
36 })?;
37
38 Platform::make_shebang_command(path, working_directory, *shebang).map_err(|output_error| {
40 Error::Cygpath {
41 recipe,
42 output_error,
43 }
44 })
45 }
46 }
47 }
48
49 pub fn script_filename(&self, recipe: &str, extension: Option<&str>) -> String {
50 let extension = extension.unwrap_or_else(|| {
51 let interpreter = match self {
52 Self::Command(interpreter) => &interpreter.command.cooked,
53 Self::Shebang(shebang) => shebang.interpreter_filename(),
54 };
55
56 match interpreter {
57 "cmd" | "cmd.exe" => ".bat",
58 "powershell" | "powershell.exe" | "pwsh" | "pwsh.exe" => ".ps1",
59 _ => "",
60 }
61 });
62
63 format!("{recipe}{extension}")
64 }
65
66 pub fn error<'src>(&self, io_error: io::Error, recipe: &'src str) -> Error<'src> {
67 match self {
68 Self::Command(Interpreter { command, arguments }) => {
69 let mut command = command.cooked.clone();
70
71 for arg in arguments {
72 command.push(' ');
73 command.push_str(&arg.cooked);
74 }
75
76 Error::Script {
77 command,
78 io_error,
79 recipe,
80 }
81 }
82 Self::Shebang(shebang) => Error::Shebang {
83 argument: shebang.argument.map(String::from),
84 command: shebang.interpreter.to_owned(),
85 io_error,
86 recipe,
87 },
88 }
89 }
90
91 pub fn script<D>(&self, recipe: &Recipe<D>, lines: &[String]) -> String {
94 let mut script = String::new();
95 let mut n = 0;
96 let shebangs = recipe
97 .body
98 .iter()
99 .take_while(|line| line.is_shebang())
100 .count();
101
102 if let Self::Shebang(shebang) = self {
103 for shebang_line in &lines[..shebangs] {
104 if shebang.include_shebang_line() {
105 script.push_str(shebang_line);
106 }
107 script.push('\n');
108 n += 1;
109 }
110 }
111
112 for (line, text) in recipe.body.iter().zip(lines).skip(n) {
113 while n < line.number {
114 script.push('\n');
115 n += 1;
116 }
117
118 script.push_str(text);
119 script.push('\n');
120 n += 1;
121 }
122
123 script
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn shebang_script_filename() {
133 #[track_caller]
134 fn case(interpreter: &str, recipe: &str, extension: Option<&str>, expected: &str) {
135 assert_eq!(
136 Executor::Shebang(Shebang::new(&format!("#!{interpreter}")).unwrap())
137 .script_filename(recipe, extension),
138 expected
139 );
140 assert_eq!(
141 Executor::Command(&Interpreter {
142 command: StringLiteral::from_raw(interpreter),
143 arguments: Vec::new()
144 })
145 .script_filename(recipe, extension),
146 expected
147 );
148 }
149
150 case("bar", "foo", Some(".sh"), "foo.sh");
151 case("pwsh.exe", "foo", Some(".sh"), "foo.sh");
152 case("cmd.exe", "foo", Some(".sh"), "foo.sh");
153 case("powershell", "foo", None, "foo.ps1");
154 case("pwsh", "foo", None, "foo.ps1");
155 case("powershell.exe", "foo", None, "foo.ps1");
156 case("pwsh.exe", "foo", None, "foo.ps1");
157 case("cmd", "foo", None, "foo.bat");
158 case("cmd.exe", "foo", None, "foo.bat");
159 case("bar", "foo", None, "foo");
160 }
161}