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}