archetect_core/actions/
conditionals.rs

1use std::path::Path;
2
3use linked_hash_map::LinkedHashMap;
4
5use log::trace;
6
7use crate::actions::{Action, ActionId};
8use crate::config::VariableInfo;
9use crate::rules::RulesContext;
10use crate::{Archetect, ArchetectError, Archetype};
11use crate::vendor::tera::{Context, Value};
12
13#[derive(Debug, Serialize, Deserialize, Clone)]
14pub struct IfAction {
15    #[serde(flatten)]
16    condition: Condition,
17    #[serde(rename = "then", alias = "actions")]
18    then_actions: Vec<ActionId>,
19    #[serde(rename = "else", skip_serializing_if = "Option::is_none")]
20    else_actions: Option<Vec<ActionId>>,
21}
22
23#[derive(Debug, Serialize, Deserialize, Clone)]
24pub enum Condition {
25    #[serde(rename = "all-of")]
26    AllOf(Vec<Condition>),
27    #[serde(rename = "conditions")]
28    Conditions(Vec<Condition>),
29    #[serde(rename = "equals")]
30    Equals(String, String),
31    #[serde(rename = "is-empty")]
32    IsEmpty(String),
33    #[serde(rename = "path-exists")]
34    PathExists(String),
35    #[serde(rename = "is-file")]
36    IsFile(String),
37    #[serde(rename = "is-directory")]
38    IsDirectory(String),
39    #[serde(rename = "switch-enabled")]
40    SwitchEnabled(String),
41    #[serde(rename = "not")]
42    Not(Box<Condition>),
43    #[serde(rename = "any-of")]
44    AnyOf(Vec<Condition>),
45    #[serde(rename = "is-true")]
46    IsTrue(String),
47}
48
49impl IfAction {
50    pub fn then_actions(&self) -> &Vec<ActionId> {
51        self.then_actions.as_ref()
52    }
53
54    pub fn else_actions(&self) -> Option<&Vec<ActionId>> {
55        self.else_actions.as_ref()
56    }
57}
58
59impl Condition {
60    pub fn evaluate<D: AsRef<Path>>(
61        &self,
62        archetect: &mut Archetect,
63        archetype: &Archetype,
64        destination: D,
65        context: &Context,
66    ) -> Result<bool, ArchetectError> {
67        match self {
68            Condition::IsEmpty(input) => {
69                if let Some(value) = context.get(input) {
70                    return match value {
71                        Value::Null => Ok(true),
72                        Value::String(string) => Ok(string.trim().is_empty()),
73                        Value::Array(array) => Ok(array.is_empty()),
74                        Value::Object(object) => Ok(object.is_empty()),
75                        Value::Bool(_) | Value::Number(_) => Ok(false),
76                    }
77                }
78                Ok(true)
79            }
80            Condition::PathExists(path) => {
81                let path = archetect.render_string(path, context)?;
82                let path = destination.as_ref().join(path);
83                Ok(path.exists())
84            }
85            Condition::IsFile(path) => {
86                let path = archetect.render_string(path, context)?;
87                let path = destination.as_ref().join(path);
88                let exists = path.exists() && path.is_file();
89                trace!("[File Exists] {}: {}", path.display(), exists);
90                Ok(exists)
91            }
92            Condition::IsDirectory(path) => {
93                let path = archetect.render_string(path, context)?;
94                let path = destination.as_ref().join(path);
95                Ok(path.exists() && path.is_dir())
96            }
97            Condition::SwitchEnabled(switch) => Ok(archetect.switches().contains(switch)),
98            Condition::Not(condition) => {
99                let value = condition.evaluate(archetect, archetype, destination, context)?;
100                Ok(!value)
101            }
102            Condition::Equals(left, right) => {
103                let left = archetect.render_string(left, context)?;
104                let right = archetect.render_string(right, context)?;
105                return Ok(left.eq(&right));
106            }
107            Condition::AnyOf(conditions) => {
108                for condition in conditions {
109                    let value = condition.evaluate(archetect, archetype, destination.as_ref(), context)?;
110                    if value {
111                        return Ok(true);
112                    }
113                }
114                Ok(false)
115            }
116            Condition::AllOf(conditions) => {
117                for condition in conditions {
118                    let value = condition.evaluate(archetect, archetype, destination.as_ref(), context)?;
119                    if !value {
120                        return Ok(false);
121                    }
122                }
123                Ok(true)
124            }
125            Condition::Conditions(conditions) => {
126                for condition in conditions {
127                    let value = condition.evaluate(archetect, archetype, destination.as_ref(), context)?;
128                    if !value {
129                        return Ok(false);
130                    }
131                }
132                Ok(true)
133            }
134            Condition::IsTrue(expression) => {
135                let result = archetect.render_string(expression, context)?;
136                return if result.trim().eq("true") {
137                    Ok(true)
138                } else {
139                    Ok(false)
140                }
141            }
142        }
143    }
144}
145
146impl Action for IfAction {
147    fn execute<D: AsRef<Path>>(
148        &self,
149        archetect: &mut Archetect,
150        archetype: &Archetype,
151        destination: D,
152        rules_context: &mut RulesContext,
153        answers: &LinkedHashMap<String, VariableInfo>,
154        context: &mut Context,
155    ) -> Result<(), ArchetectError> {
156        if self.condition.evaluate(archetect, archetype, destination.as_ref(), context)? {
157            let action: ActionId = self.then_actions().into();
158            action.execute(
159                archetect,
160                archetype,
161                destination.as_ref(),
162                rules_context,
163                answers,
164                context,
165            )?;
166        } else {
167            if let Some(actions) = self.else_actions() {
168                let action: ActionId = actions[..].into();
169                action.execute(
170                    archetect,
171                    archetype,
172                    destination.as_ref(),
173                    rules_context,
174                    answers,
175                    context,
176                )?;
177            }
178        }
179
180        Ok(())
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use crate::actions::conditionals::{Condition, IfAction};
187    use crate::actions::render::{DirectoryOptions, RenderAction};
188    use crate::actions::ActionId;
189
190    #[test]
191    pub fn test_serialize() -> Result<(), serde_yaml::Error> {
192        let action = IfAction {
193            condition: Condition::AllOf(vec![
194                Condition::IsFile("example.txt".to_owned()),
195                Condition::IsDirectory("src/main/java".to_owned()),
196                Condition::PathExists("{{ service }}".to_owned()),
197                Condition::Equals("{{ one }}".to_owned(), "{{ two }}".to_owned()),
198            ]),
199            then_actions: vec![ActionId::Render(RenderAction::Directory(DirectoryOptions::new(".")))],
200            else_actions: None,
201        };
202
203        let yaml = serde_yaml::to_string(&action)?;
204        println!("{}", yaml);
205
206        Ok(())
207    }
208}