Support for polyglot recipes!

This commit is contained in:
Casey Rodarmor 2016-10-07 17:56:52 -07:00
parent 2e97f3f026
commit 25785eea77

View File

@ -2,6 +2,7 @@
mod tests; mod tests;
extern crate regex; extern crate regex;
extern crate tempdir;
use std::io::prelude::*; use std::io::prelude::*;
@ -10,6 +11,8 @@ use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::fmt::Display; use std::fmt::Display;
use regex::Regex; use regex::Regex;
use std::os::unix::fs::PermissionsExt;
macro_rules! warn { macro_rules! warn {
($($arg:tt)*) => {{ ($($arg:tt)*) => {{
extern crate std; extern crate std;
@ -81,18 +84,51 @@ fn error_from_signal<'a>(recipe: &'a str, exit_status: process::ExitStatus) -> R
impl<'a> Recipe<'a> { impl<'a> Recipe<'a> {
fn run(&self) -> Result<(), RunError<'a>> { fn run(&self) -> Result<(), RunError<'a>> {
// TODO: if shebang, run as script if self.shebang {
for command in &self.lines { let tmp = try!(
let mut command = *command; tempdir::TempDir::new("j")
if !command.starts_with("@") { .map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})
warn!("{}", command); );
} else { let mut path = tmp.path().to_path_buf();
command = &command[1..]; path.push(self.name);
{
let mut f = try!(
fs::File::create(&path)
.map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})
);
let mut text = String::new();
// add the shebang
text += self.lines[0];
text += "\n";
// add blank lines so that lines in the generated script
// have the same line number as the corresponding lines
// in the justfile
for _ in 1..(self.line_number + 2) {
text += "\n"
}
for line in &self.lines[1..] {
text += line;
text += "\n";
}
try!(
f.write_all(text.as_bytes())
.map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})
);
} }
let status = process::Command::new("sh")
.arg("-cu") // get current permissions
.arg(command) let mut perms = try!(
.status(); fs::metadata(&path)
.map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})
).permissions();
// make the script executable
let current_mode = perms.mode();
perms.set_mode(current_mode | 0o100);
try!(fs::set_permissions(&path, perms).map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error}));
// run it!
let status = process::Command::new(path).status();
try!(match status { try!(match status {
Ok(exit_status) => if let Some(code) = exit_status.code() { Ok(exit_status) => if let Some(code) = exit_status.code() {
if code == 0 { if code == 0 {
@ -103,8 +139,33 @@ impl<'a> Recipe<'a> {
} else { } else {
Err(error_from_signal(self.name, exit_status)) Err(error_from_signal(self.name, exit_status))
}, },
Err(io_error) => Err(RunError::IoError{recipe: self.name, io_error: io_error}) Err(io_error) => Err(RunError::TmpdirIoError{recipe: self.name, io_error: io_error})
}); });
} else {
for command in &self.lines {
let mut command = *command;
if !command.starts_with("@") {
warn!("{}", command);
} else {
command = &command[1..];
}
let status = process::Command::new("sh")
.arg("-cu")
.arg(command)
.status();
try!(match status {
Ok(exit_status) => if let Some(code) = exit_status.code() {
if code == 0 {
Ok(())
} else {
Err(RunError::Code{recipe: self.name, code: code})
}
} else {
Err(error_from_signal(self.name, exit_status))
},
Err(io_error) => Err(RunError::IoError{recipe: self.name, io_error: io_error})
});
}
} }
Ok(()) Ok(())
} }
@ -261,8 +322,8 @@ impl<'a> Display for Error<'a> {
match self.text.lines().nth(self.line) { match self.text.lines().nth(self.line) {
Some(line) => try!(write!(f, "{}", line)), Some(line) => try!(write!(f, "{}", line)),
None => die!("internal error: Error has invalid line number: {}", self.line), None => try!(write!(f, "internal error: Error has invalid line number: {}", self.line)),
} };
Ok(()) Ok(())
} }
@ -338,6 +399,7 @@ pub enum RunError<'a> {
Code{recipe: &'a str, code: i32}, Code{recipe: &'a str, code: i32},
UnknownFailure{recipe: &'a str}, UnknownFailure{recipe: &'a str},
IoError{recipe: &'a str, io_error: io::Error}, IoError{recipe: &'a str, io_error: io::Error},
TmpdirIoError{recipe: &'a str, io_error: io::Error},
} }
impl<'a> Display for RunError<'a> { impl<'a> Display for RunError<'a> {
@ -366,6 +428,8 @@ impl<'a> Display for RunError<'a> {
_ => write!(f, "Recipe \"{}\" could not be run because of an IO error while launching the `sh`:\n{}", recipe, io_error), _ => write!(f, "Recipe \"{}\" could not be run because of an IO error while launching the `sh`:\n{}", recipe, io_error),
}); });
}, },
&RunError::TmpdirIoError{recipe, ref io_error} =>
try!(write!(f, "Recipe \"{}\" could not be run because of an IO error while trying to create a temporary directory or write a file to that directory`:\n{}", recipe, io_error)),
} }
Ok(()) Ok(())
} }