Lines
65.81 %
Functions
33.33 %
Branches
100 %
use anyhow::Context as _;
use schemars::JsonSchema;
use serde::Deserialize;
use std::io::{
BufRead as _,
BufReader,
};
use std::thread;
use super::{
Shell,
TaskContext,
use crate::defaults::default_verbose;
use crate::handle_output;
use crate::schema::get_output_handler;
/// This struct represents a precondition that must be met before a task can be
/// executed.
#[derive(Debug, Default, Deserialize, JsonSchema)]
pub struct Precondition {
/// The command to run
pub command: String,
/// The message to display if the command fails
#[serde(default)]
pub message: Option<String>,
/// The shell to use to run the command
pub shell: Option<Shell>,
/// The working directory to run the command in
pub work_dir: Option<String>,
/// Show verbose output
pub verbose: Option<bool>,
}
impl Precondition {
pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
assert!(!self.command.is_empty());
let verbose = self.verbose(context);
let stdout = get_output_handler(verbose);
let stderr = get_output_handler(verbose);
let mut cmd = self
.shell
.as_ref()
.map(|shell| shell.proc())
.unwrap_or_else(|| context.shell().proc());
cmd.arg(self.command.clone()).stdout(stdout).stderr(stderr);
if let Some(work_dir) = self.resolved_work_dir(context) {
cmd.current_dir(work_dir);
// Inject environment variables
for (key, value) in context.env_vars.iter() {
cmd.env(key, value);
let mut cmd = cmd.spawn()?;
if verbose {
handle_output!(cmd.stdout, context);
handle_output!(cmd.stderr, context);
let status = cmd.wait()?;
if !status.success() {
if let Some(message) = &self.message {
anyhow::bail!("Precondition failed - {}", message);
} else {
anyhow::bail!("Precondition failed - {}", self.command);
Ok(())
fn verbose(&self, context: &TaskContext) -> bool {
self.verbose.or(context.verbose).unwrap_or(default_verbose())
fn resolved_work_dir(&self, context: &TaskContext) -> Option<std::path::PathBuf> {
self
.work_dir
.map(|work_dir| context.resolve_from_config(work_dir))
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_precondition_1() -> anyhow::Result<()> {
{
let yaml = "
command: 'echo \"Hello, World!\"'
message: 'This is a message'
";
let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
assert_eq!(precondition.command, "echo \"Hello, World!\"");
assert_eq!(precondition.message, Some("This is a message".into()));
assert_eq!(precondition.work_dir, None);
assert_eq!(precondition.verbose, None);
fn test_precondition_2() -> anyhow::Result<()> {
assert_eq!(precondition.message, None);
fn test_precondition_3() -> anyhow::Result<()> {
message: null
fn test_precondition_4() -> anyhow::Result<()> {
work_dir: /tmp
assert_eq!(precondition.work_dir, Some("/tmp".into()));
fn test_precondition_5() -> anyhow::Result<()> {
verbose: true
assert_eq!(precondition.verbose, Some(true));
fn test_precondition_6() -> anyhow::Result<()> {
command: ls -la
message: Listing directory contents
shell: bash
verbose: false
assert_eq!(precondition.command, "ls -la");
assert_eq!(
precondition.message,
Some("Listing directory contents".to_string())
);
if let Some(shell) = precondition.shell {
assert_eq!(shell.cmd(), "bash".to_string());
panic!("Expected shell to be Some");
assert_eq!(precondition.verbose, Some(false));