Split Recipe::run into Recipe::{run_shebang,run_linewise} (#1270)
This commit is contained in:
parent
64b4d71d66
commit
4a4c669db9
7
justfile
7
justfile
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
424
src/recipe.rs
424
src/recipe.rs
@ -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,
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>>();
|
||||||
|
Loading…
Reference in New Issue
Block a user