mk_lib/schema/
precondition.rs

1use anyhow::Context as _;
2use serde::Deserialize;
3use std::io::{
4  BufRead as _,
5  BufReader,
6};
7use std::thread;
8
9use super::{
10  Shell,
11  TaskContext,
12};
13use crate::defaults::default_verbose;
14use crate::handle_output;
15use crate::schema::get_output_handler;
16
17/// This struct represents a precondition that must be met before a task can be
18/// executed.
19#[derive(Debug, Default, Deserialize)]
20pub struct Precondition {
21  /// The command to run
22  pub command: String,
23
24  /// The message to display if the command fails
25  #[serde(default)]
26  pub message: Option<String>,
27
28  /// The shell to use to run the command
29  #[serde(default)]
30  pub shell: Option<Shell>,
31
32  /// The working directory to run the command in
33  #[serde(default)]
34  pub work_dir: Option<String>,
35
36  /// Show verbose output
37  #[serde(default)]
38  pub verbose: Option<bool>,
39}
40
41impl Precondition {
42  pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
43    assert!(!self.command.is_empty());
44
45    let verbose = self.verbose(context);
46
47    let stdout = get_output_handler(verbose);
48    let stderr = get_output_handler(verbose);
49
50    let mut cmd = self
51      .shell
52      .as_ref()
53      .map(|shell| shell.proc())
54      .unwrap_or_else(|| context.shell().proc());
55
56    cmd.arg(self.command.clone()).stdout(stdout).stderr(stderr);
57
58    if self.work_dir.is_some() {
59      cmd.current_dir(self.work_dir.as_ref().with_context(|| "Failed to get work_dir")?);
60    }
61
62    // Inject environment variables
63    for (key, value) in context.env_vars.iter() {
64      cmd.env(key, value);
65    }
66
67    let mut cmd = cmd.spawn()?;
68
69    if verbose {
70      handle_output!(cmd.stdout, context);
71      handle_output!(cmd.stderr, context);
72    }
73
74    let status = cmd.wait()?;
75    if !status.success() {
76      if let Some(message) = &self.message {
77        anyhow::bail!("Precondition failed - {}", message);
78      } else {
79        anyhow::bail!("Precondition failed - {}", self.command);
80      }
81    }
82
83    Ok(())
84  }
85
86  fn verbose(&self, context: &TaskContext) -> bool {
87    self.verbose.or(context.verbose).unwrap_or(default_verbose())
88  }
89}
90
91#[cfg(test)]
92mod test {
93  use super::*;
94
95  #[test]
96  fn test_precondition_1() -> anyhow::Result<()> {
97    {
98      let yaml = "
99        command: 'echo \"Hello, World!\"'
100        message: 'This is a message'
101      ";
102      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
103
104      assert_eq!(precondition.command, "echo \"Hello, World!\"");
105      assert_eq!(precondition.message, Some("This is a message".into()));
106      assert_eq!(precondition.work_dir, None);
107      assert_eq!(precondition.verbose, None);
108
109      Ok(())
110    }
111  }
112
113  #[test]
114  fn test_precondition_2() -> anyhow::Result<()> {
115    {
116      let yaml = "
117        command: 'echo \"Hello, World!\"'
118      ";
119      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
120
121      assert_eq!(precondition.command, "echo \"Hello, World!\"");
122      assert_eq!(precondition.message, None);
123      assert_eq!(precondition.work_dir, None);
124      assert_eq!(precondition.verbose, None);
125
126      Ok(())
127    }
128  }
129
130  #[test]
131  fn test_precondition_3() -> anyhow::Result<()> {
132    {
133      let yaml = "
134        command: 'echo \"Hello, World!\"'
135        message: null
136      ";
137      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
138
139      assert_eq!(precondition.command, "echo \"Hello, World!\"");
140      assert_eq!(precondition.message, None);
141      assert_eq!(precondition.work_dir, None);
142      assert_eq!(precondition.verbose, None);
143
144      Ok(())
145    }
146  }
147
148  #[test]
149  fn test_precondition_4() -> anyhow::Result<()> {
150    {
151      let yaml = "
152        command: 'echo \"Hello, World!\"'
153        work_dir: /tmp
154      ";
155      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
156
157      assert_eq!(precondition.command, "echo \"Hello, World!\"");
158      assert_eq!(precondition.message, None);
159      assert_eq!(precondition.work_dir, Some("/tmp".into()));
160      assert_eq!(precondition.verbose, None);
161
162      Ok(())
163    }
164  }
165
166  #[test]
167  fn test_precondition_5() -> anyhow::Result<()> {
168    {
169      let yaml = "
170        command: 'echo \"Hello, World!\"'
171        verbose: true
172      ";
173      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
174
175      assert_eq!(precondition.command, "echo \"Hello, World!\"");
176      assert_eq!(precondition.message, None);
177      assert_eq!(precondition.work_dir, None);
178      assert_eq!(precondition.verbose, Some(true));
179
180      Ok(())
181    }
182  }
183
184  #[test]
185  fn test_precondition_6() -> anyhow::Result<()> {
186    {
187      let yaml = "
188        command: ls -la
189        message: Listing directory contents
190        shell: bash
191        work_dir: /tmp
192        verbose: false
193      ";
194      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
195
196      assert_eq!(precondition.command, "ls -la");
197      assert_eq!(
198        precondition.message,
199        Some("Listing directory contents".to_string())
200      );
201      if let Some(shell) = precondition.shell {
202        assert_eq!(shell.cmd(), "bash".to_string());
203      } else {
204        panic!("Expected shell to be Some");
205      }
206      assert_eq!(precondition.work_dir, Some("/tmp".into()));
207      assert_eq!(precondition.verbose, Some(false));
208
209      Ok(())
210    }
211  }
212}