From 25785eea77540d5519ce890d80f2f0a42926030b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 7 Oct 2016 17:56:52 -0700 Subject: [PATCH] Support for polyglot recipes! --- src/lib.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a316854..356fe74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ mod tests; extern crate regex; +extern crate tempdir; use std::io::prelude::*; @@ -10,6 +11,8 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::fmt::Display; use regex::Regex; +use std::os::unix::fs::PermissionsExt; + macro_rules! warn { ($($arg:tt)*) => {{ extern crate std; @@ -81,18 +84,51 @@ fn error_from_signal<'a>(recipe: &'a str, exit_status: process::ExitStatus) -> R impl<'a> Recipe<'a> { fn run(&self) -> Result<(), RunError<'a>> { - // TODO: if shebang, run as script - for command in &self.lines { - let mut command = *command; - if !command.starts_with("@") { - warn!("{}", command); - } else { - command = &command[1..]; + if self.shebang { + let tmp = try!( + tempdir::TempDir::new("j") + .map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error}) + ); + let mut path = tmp.path().to_path_buf(); + 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") - .arg(command) - .status(); + + // get current permissions + let mut perms = try!( + 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 { Ok(exit_status) => if let Some(code) = exit_status.code() { if code == 0 { @@ -103,8 +139,33 @@ impl<'a> Recipe<'a> { } else { 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(()) } @@ -261,8 +322,8 @@ impl<'a> Display for Error<'a> { match self.text.lines().nth(self.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(()) } @@ -338,6 +399,7 @@ pub enum RunError<'a> { Code{recipe: &'a str, code: i32}, UnknownFailure{recipe: &'a str}, IoError{recipe: &'a str, io_error: io::Error}, + TmpdirIoError{recipe: &'a str, io_error: io::Error}, } 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), }); }, + &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(()) }