proplate_core/template/
op.rs

1use std::{collections::HashMap, fs, path::Path};
2
3use proplate_errors::{ProplateError, ProplateErrorKind, ProplateResult};
4use serde::{Deserialize, Serialize};
5
6use super::interpolation::Interpolate;
7use crate::fs as pfs;
8
9#[derive(Serialize, Deserialize, Debug, Clone)]
10pub enum StringCompareOp {
11  Eq,
12  NotEqual,
13}
14
15#[derive(Serialize, Deserialize, Debug, Clone)]
16pub struct Condition {
17  pub lhs: String,
18  pub op: StringCompareOp,
19  pub rhs: String,
20}
21
22#[derive(Serialize, Deserialize, Debug)]
23pub enum Operation {
24  // Separate op to avoid ambiguity
25  Copy { file: String, dest: String },
26  CopyDir { path: String, dest: String },
27  Remove { files: Vec<String> },
28}
29
30#[derive(Serialize, Deserialize, Debug)]
31pub struct AdditionalOperation {
32  #[serde(default = "Vec::new")]
33  pub conditions: Vec<Condition>,
34  /// operations to execute if the above conditions are evaluated as true
35  pub operations: Vec<Operation>,
36}
37
38impl Condition {
39  fn eval(&self) -> bool {
40    match self.op {
41      StringCompareOp::Eq => self.lhs == self.rhs,
42      StringCompareOp::NotEqual => self.lhs != self.rhs,
43    }
44  }
45
46  pub fn eval_in_ctx(&self, ctx: &HashMap<String, String>) -> bool {
47    let mut c = self.clone();
48    c.lhs = c.lhs.interpolate(ctx);
49    c.rhs = c.rhs.interpolate(ctx);
50    c.eval()
51  }
52}
53
54pub trait Execute {
55  fn execute(&self, ctx: &HashMap<String, String>) -> ProplateResult<()>;
56}
57
58impl Execute for Operation {
59  fn execute(&self, _ctx: &HashMap<String, String>) -> ProplateResult<()> {
60    match self {
61      Operation::Copy { file, dest } => {
62        let src = Path::new(&file);
63        let dest = Path::new(&dest);
64        fs::copy(src, dest).map_err(|e| {
65          ProplateError::create(ProplateErrorKind::Fs {
66            concerned_paths: vec![src.display().to_string(), dest.display().to_string()],
67            operation: "copy".into(),
68          })
69          .with_ctx("op::execute::Copy")
70          .with_cause(&e.to_string())
71        })?;
72        Ok(())
73      }
74      Operation::CopyDir { path, dest } => {
75        let path = Path::new(path);
76        let dest = Path::new(dest);
77        pfs::copy_fdir(path, dest, None).map_err(|e| {
78          ProplateError::create(ProplateErrorKind::Fs {
79            concerned_paths: vec![path.display().to_string(), dest.display().to_string()],
80            operation: "copy_dir".into(),
81          })
82          .with_ctx("op::execute::CopyDir")
83          .with_cause(&e.to_string())
84        })?;
85        Ok(())
86      }
87      Operation::Remove { files } => {
88        for file in files {
89          let src = Path::new(&file);
90          pfs::remove_fdir(src).map_err(|e| {
91            ProplateError::create(ProplateErrorKind::Fs {
92              concerned_paths: vec![src.display().to_string()],
93              operation: "remove_fdir".into(),
94            })
95            .with_ctx("op::execute::Remove")
96            .with_cause(&e.to_string())
97          })?;
98        }
99        Ok(())
100      }
101    }
102  }
103}
104
105impl Execute for AdditionalOperation {
106  fn execute(&self, ctx: &HashMap<String, String>) -> ProplateResult<()> {
107    // eval condition or true if it is empty or missing
108    let conditions = &self.conditions;
109    let true_ = match conditions.is_empty() {
110      true => true,
111      false => conditions.iter().all(|c| c.eval_in_ctx(ctx)),
112    };
113
114    if true_ {
115      for operation in &self.operations {
116        operation.execute(ctx)?;
117      }
118    }
119
120    Ok(())
121  }
122}