use super::*;
pub(crate) enum Executor<'a> {
Command(Interpreter<String>),
Shebang(Shebang<'a>),
}
impl Executor<'_> {
pub(crate) fn command<'src>(
&self,
config: &Config,
path: &Path,
recipe: &'src str,
working_directory: Option<&Path>,
) -> RunResult<'src, Command> {
match self {
Self::Command(interpreter) => {
let mut command = Command::resolve(&interpreter.command);
if let Some(working_directory) = working_directory {
command.current_dir(working_directory);
}
for arg in &interpreter.arguments {
command.arg(arg);
}
command.arg(path);
Ok(command)
}
Self::Shebang(shebang) => {
Platform::set_execute_permission(path).map_err(|error| Error::TempdirIo {
recipe,
io_error: error,
})?;
Platform::make_shebang_command(config, path, *shebang, working_directory).map_err(
|output_error| Error::Cygpath {
recipe,
output_error,
},
)
}
}
}
fn shell_kind(&self) -> ShellKind {
match self {
Self::Command(interpreter) => &interpreter.command,
Self::Shebang(shebang) => shebang.interpreter_filename(),
}
.into()
}
pub(crate) fn script_filename(&self, recipe: &str, extension: Option<&str>) -> String {
format!(
"{recipe}{}",
extension.unwrap_or_else(|| self.shell_kind().extension()),
)
}
pub(crate) fn error<'src>(&self, io_error: io::Error, recipe: &'src str) -> Error<'src> {
match self {
Self::Command(Interpreter { command, arguments }) => {
let mut command = command.clone();
for arg in arguments {
command.push(' ');
command.push_str(arg);
}
Error::Script {
command,
io_error,
recipe,
}
}
Self::Shebang(shebang) => Error::Shebang {
argument: shebang.argument.map(String::from),
command: shebang.interpreter.to_owned(),
io_error,
recipe,
},
}
}
pub(crate) fn script<D>(&self, recipe: &Recipe<D>, lines: &[String]) -> String {
let mut script = String::new();
let mut n = 0;
let shebangs = recipe
.body
.iter()
.take_while(|line| line.is_shebang())
.count();
if let Self::Shebang(shebang) = self {
for shebang_line in &lines[..shebangs] {
if shebang.include_shebang_line() {
script.push_str(shebang_line);
}
script.push('\n');
n += 1;
}
}
for (line, text) in recipe.body.iter().zip(lines).skip(n) {
while n < line.number {
script.push('\n');
n += 1;
}
script.push_str(text);
script.push('\n');
n += 1;
}
script
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shebang_script_filename() {
#[track_caller]
fn case(interpreter: &str, recipe: &str, extension: Option<&str>, expected: &str) {
assert_eq!(
Executor::Shebang(Shebang::new(&format!("#!{interpreter}")).unwrap())
.script_filename(recipe, extension),
expected
);
assert_eq!(
Executor::Command(Interpreter {
command: interpreter.into(),
arguments: Vec::new()
})
.script_filename(recipe, extension),
expected
);
}
case("bar", "foo", Some(".sh"), "foo.sh");
case("pwsh.exe", "foo", Some(".sh"), "foo.sh");
case("cmd.exe", "foo", Some(".sh"), "foo.sh");
case("powershell", "foo", None, "foo.ps1");
case("pwsh", "foo", None, "foo.ps1");
case("powershell.exe", "foo", None, "foo.ps1");
case("pwsh.exe", "foo", None, "foo.ps1");
case("cmd", "foo", None, "foo.bat");
case("cmd.exe", "foo", None, "foo.bat");
case("bar", "foo", None, "foo");
}
}