Split Recipe::run into Recipe::{run_shebang,run_linewise} (#1270)

This commit is contained in:
Casey Rodarmor 2022-07-20 18:46:52 -07:00 committed by GitHub
parent 64b4d71d66
commit 4a4c669db9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 231 additions and 202 deletions

View File

@ -17,6 +17,13 @@ export JUST_LOG := log
test: test:
cargo test cargo test
ci: build-book
cargo test --all
cargo clippy --all --all-targets
cargo fmt --all -- --check
./bin/forbid
cargo update --locked --package just
fuzz: fuzz:
cargo +nightly fuzz run fuzz-compiler cargo +nightly fuzz run fuzz-compiler

View File

@ -11,6 +11,7 @@
clippy::shadow_unrelated, clippy::shadow_unrelated,
clippy::struct_excessive_bools, clippy::struct_excessive_bools,
clippy::too_many_lines, clippy::too_many_lines,
clippy::type_repetition_in_bounds,
clippy::wildcard_imports clippy::wildcard_imports
)] )]

View File

@ -85,225 +85,247 @@ impl<'src, D> Recipe<'src, D> {
); );
} }
let mut evaluator = let evaluator =
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search); Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
if self.shebang { if self.shebang {
let mut evaluated_lines = vec![]; self.run_shebang(context, dotenv, &scope, positional, config, evaluator)
for line in &self.body { } else {
evaluated_lines.push(evaluator.evaluate_line(line, false)?); self.run_linewise(context, dotenv, &scope, positional, config, evaluator)
} }
}
if config.verbosity.loud() && (config.dry_run || self.quiet) { pub(crate) fn run_linewise<'run>(
for line in &evaluated_lines { &self,
eprintln!("{}", line); context: &RecipeContext<'src, 'run>,
} dotenv: &BTreeMap<String, String>,
} scope: &Scope<'src, 'run>,
positional: &[String],
if config.dry_run { config: &Config,
mut evaluator: Evaluator<'src, 'run>,
) -> RunResult<'src, ()> {
let mut lines = self.body.iter().peekable();
let mut line_number = self.line_number() + 1;
loop {
if lines.peek().is_none() {
return Ok(()); return Ok(());
} }
let mut evaluated = String::new();
let shebang_line = evaluated_lines.first().ok_or_else(|| Error::Internal { let mut continued = false;
message: "evaluated_lines was empty".to_owned(), let quiet_command = lines.peek().map_or(false, |line| line.is_quiet());
})?; let infallible_command = lines.peek().map_or(false, |line| line.is_infallible());
let shebang = Shebang::new(shebang_line).ok_or_else(|| Error::Internal {
message: format!("bad shebang line: {}", shebang_line),
})?;
let tmp = tempfile::Builder::new()
.prefix("just")
.tempdir()
.map_err(|error| Error::TmpdirIo {
recipe: self.name(),
io_error: error,
})?;
let mut path = tmp.path().to_path_buf();
path.push(shebang.script_filename(self.name()));
{
let mut f = fs::File::create(&path).map_err(|error| Error::TmpdirIo {
recipe: self.name(),
io_error: error,
})?;
let mut text = String::new();
if shebang.include_shebang_line() {
text += &evaluated_lines[0];
} else {
text += "\n";
}
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 &evaluated_lines[1..] {
text += line;
text += "\n";
}
if config.verbosity.grandiloquent() {
eprintln!("{}", config.color.doc().stderr().paint(&text));
}
f.write_all(text.as_bytes())
.map_err(|error| Error::TmpdirIo {
recipe: self.name(),
io_error: error,
})?;
}
// make the script executable
Platform::set_execute_permission(&path).map_err(|error| Error::TmpdirIo {
recipe: self.name(),
io_error: error,
})?;
// create a command to run the script
let mut command =
Platform::make_shebang_command(&path, &context.search.working_directory, shebang).map_err(
|output_error| Error::Cygpath {
recipe: self.name(),
output_error,
},
)?;
if context.settings.positional_arguments {
command.args(positional);
}
command.export(context.settings, dotenv, &scope);
// run it!
match InterruptHandler::guard(|| command.status()) {
Ok(exit_status) => {
if let Some(code) = exit_status.code() {
if code != 0 {
return Err(Error::Code {
recipe: self.name(),
line_number: None,
code,
});
}
} else {
return Err(error_from_signal(self.name(), None, exit_status));
}
}
Err(io_error) => {
return Err(Error::Shebang {
recipe: self.name(),
command: shebang.interpreter.to_owned(),
argument: shebang.argument.map(String::from),
io_error,
});
}
};
} else {
let mut lines = self.body.iter().peekable();
let mut line_number = self.line_number() + 1;
loop { loop {
if lines.peek().is_none() { if lines.peek().is_none() {
break; break;
} }
let mut evaluated = String::new(); let line = lines.next().unwrap();
let mut continued = false; line_number += 1;
let quiet_command = lines.peek().map_or(false, |line| line.is_quiet()); evaluated += &evaluator.evaluate_line(line, continued)?;
let infallible_command = lines.peek().map_or(false, |line| line.is_infallible()); if line.is_continuation() {
loop { continued = true;
if lines.peek().is_none() { evaluated.pop();
break; } else {
} break;
let line = lines.next().unwrap();
line_number += 1;
evaluated += &evaluator.evaluate_line(line, continued)?;
if line.is_continuation() {
continued = true;
evaluated.pop();
} else {
break;
}
} }
let mut command = evaluated.as_str(); }
let mut command = evaluated.as_str();
if quiet_command { if quiet_command {
command = &command[1..]; command = &command[1..];
} }
if infallible_command { if infallible_command {
command = &command[1..]; command = &command[1..];
} }
if command.is_empty() { if command.is_empty() {
continue; continue;
} }
if config.dry_run if config.dry_run
|| config.verbosity.loquacious() || config.verbosity.loquacious()
|| !((quiet_command ^ self.quiet) || config.verbosity.quiet()) || !((quiet_command ^ self.quiet) || config.verbosity.quiet())
{ {
let color = if config.highlight { let color = if config.highlight {
config.color.command() config.color.command()
} else { } else {
config.color config.color
};
eprintln!("{}", color.stderr().paint(command));
}
if config.dry_run {
continue;
}
let mut cmd = context.settings.shell_command(config);
cmd.current_dir(&context.search.working_directory);
cmd.arg(command);
if context.settings.positional_arguments {
cmd.arg(self.name.lexeme());
cmd.args(positional);
}
if config.verbosity.quiet() {
cmd.stderr(Stdio::null());
cmd.stdout(Stdio::null());
}
cmd.export(context.settings, dotenv, &scope);
match InterruptHandler::guard(|| cmd.status()) {
Ok(exit_status) => {
if let Some(code) = exit_status.code() {
if code != 0 && !infallible_command {
return Err(Error::Code {
recipe: self.name(),
line_number: Some(line_number),
code,
});
}
} else {
return Err(error_from_signal(
self.name(),
Some(line_number),
exit_status,
));
}
}
Err(io_error) => {
return Err(Error::Io {
recipe: self.name(),
io_error,
});
}
}; };
eprintln!("{}", color.stderr().paint(command));
}
if config.dry_run {
continue;
}
let mut cmd = context.settings.shell_command(config);
cmd.current_dir(&context.search.working_directory);
cmd.arg(command);
if context.settings.positional_arguments {
cmd.arg(self.name.lexeme());
cmd.args(positional);
}
if config.verbosity.quiet() {
cmd.stderr(Stdio::null());
cmd.stdout(Stdio::null());
}
cmd.export(context.settings, dotenv, scope);
match InterruptHandler::guard(|| cmd.status()) {
Ok(exit_status) => {
if let Some(code) = exit_status.code() {
if code != 0 && !infallible_command {
return Err(Error::Code {
recipe: self.name(),
line_number: Some(line_number),
code,
});
}
} else {
return Err(error_from_signal(
self.name(),
Some(line_number),
exit_status,
));
}
}
Err(io_error) => {
return Err(Error::Io {
recipe: self.name(),
io_error,
});
}
};
}
}
pub(crate) fn run_shebang<'run>(
&self,
context: &RecipeContext<'src, 'run>,
dotenv: &BTreeMap<String, String>,
scope: &Scope<'src, 'run>,
positional: &[String],
config: &Config,
mut evaluator: Evaluator<'src, 'run>,
) -> RunResult<'src, ()> {
let mut evaluated_lines = vec![];
for line in &self.body {
evaluated_lines.push(evaluator.evaluate_line(line, false)?);
}
if config.verbosity.loud() && (config.dry_run || self.quiet) {
for line in &evaluated_lines {
eprintln!("{}", line);
} }
} }
Ok(())
if config.dry_run {
return Ok(());
}
let shebang_line = evaluated_lines.first().ok_or_else(|| Error::Internal {
message: "evaluated_lines was empty".to_owned(),
})?;
let shebang = Shebang::new(shebang_line).ok_or_else(|| Error::Internal {
message: format!("bad shebang line: {}", shebang_line),
})?;
let tmp = tempfile::Builder::new()
.prefix("just")
.tempdir()
.map_err(|error| Error::TmpdirIo {
recipe: self.name(),
io_error: error,
})?;
let mut path = tmp.path().to_path_buf();
path.push(shebang.script_filename(self.name()));
{
let mut f = fs::File::create(&path).map_err(|error| Error::TmpdirIo {
recipe: self.name(),
io_error: error,
})?;
let mut text = String::new();
if shebang.include_shebang_line() {
text += &evaluated_lines[0];
} else {
text += "\n";
}
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 &evaluated_lines[1..] {
text += line;
text += "\n";
}
if config.verbosity.grandiloquent() {
eprintln!("{}", config.color.doc().stderr().paint(&text));
}
f.write_all(text.as_bytes())
.map_err(|error| Error::TmpdirIo {
recipe: self.name(),
io_error: error,
})?;
}
// make the script executable
Platform::set_execute_permission(&path).map_err(|error| Error::TmpdirIo {
recipe: self.name(),
io_error: error,
})?;
// create a command to run the script
let mut command =
Platform::make_shebang_command(&path, &context.search.working_directory, shebang).map_err(
|output_error| Error::Cygpath {
recipe: self.name(),
output_error,
},
)?;
if context.settings.positional_arguments {
command.args(positional);
}
command.export(context.settings, dotenv, scope);
// run it!
match InterruptHandler::guard(|| command.status()) {
Ok(exit_status) => exit_status.code().map_or_else(
|| Err(error_from_signal(self.name(), None, exit_status)),
|code| {
if code == 0 {
Ok(())
} else {
Err(Error::Code {
recipe: self.name(),
line_number: None,
code,
})
}
},
),
Err(io_error) => Err(Error::Shebang {
recipe: self.name(),
command: shebang.interpreter.to_owned(),
argument: shebang.argument.map(String::from),
io_error,
}),
}
} }
} }

View File

@ -254,7 +254,6 @@ impl Subcommand {
let stdout = String::from_utf8_lossy(&output.stdout); let stdout = String::from_utf8_lossy(&output.stdout);
let recipes = stdout let recipes = stdout
.trim()
.split_whitespace() .split_whitespace()
.map(str::to_owned) .map(str::to_owned)
.collect::<Vec<String>>(); .collect::<Vec<String>>();