pub_just/
executor.rs

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        // make script executable
33        Platform::set_execute_permission(path).map_err(|error| Error::TempdirIo {
34          recipe,
35          io_error: error,
36        })?;
37
38        // create command to run script
39        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  // Script text for `recipe` given evaluated `lines` including blanks so line
92  // numbers in errors from generated script match justfile source lines.
93  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}