archetect_core/actions/
foreach.rs

1use std::path::Path;
2
3use linked_hash_map::LinkedHashMap;
4
5use crate::actions::{Action, ActionId, LoopContext};
6use crate::config::VariableInfo;
7use crate::rules::RulesContext;
8use crate::{Archetect, ArchetectError, Archetype};
9use crate::vendor::tera::Context;
10
11#[derive(Debug, Serialize, Deserialize, Clone)]
12pub struct ForEachAction {
13    #[serde(rename = "in")]
14    source: ForEachSource,
15    #[serde(rename = "do", alias = "actions")]
16    actions: Vec<ActionId>,
17}
18
19#[derive(Debug, Serialize, Deserialize, Clone)]
20pub enum ForEachSource {
21    #[serde(rename = "variable")]
22    Variable(String),
23    #[serde(rename = "split")]
24    Split(SplitOptions),
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone)]
28pub struct SplitOptions {
29    input: String,
30    separator: String,
31}
32
33impl ForEachAction {
34    pub fn actions(&self) -> &Vec<ActionId> {
35        self.actions.as_ref()
36    }
37}
38
39impl Action for ForEachAction {
40    fn execute<D: AsRef<Path>>(
41        &self,
42        archetect: &mut Archetect,
43        archetype: &Archetype,
44        destination: D,
45        rules_context: &mut RulesContext,
46        answers: &LinkedHashMap<String, VariableInfo>,
47        context: &mut Context,
48    ) -> Result<(), ArchetectError> {
49        match &self.source {
50            ForEachSource::Variable(identifier) => {
51                if let Some(value) = context.get(identifier) {
52                    if let Some(items) = value.as_array() {
53                        let mut context = context.clone();
54                        let mut rules_context = rules_context.clone();
55                        rules_context.set_break_triggered(false);
56
57                        let mut loop_context = LoopContext::new();
58
59                        for item in items {
60                            if rules_context.break_triggered() {
61                                break;
62                            }
63                            context.insert("item", item);
64                            context.insert("loop", &loop_context);
65                            let action: ActionId = self.actions().into();
66                            action.execute(
67                                archetect,
68                                archetype,
69                                destination.as_ref(),
70                                &mut rules_context,
71                                answers,
72                                &mut context,
73                            )?;
74                            loop_context.increment();
75                        }
76                    } else {
77                        let item = {
78                            if value.is_number() {
79                                value.as_i64().unwrap().to_string()
80                            } else if value.is_boolean() {
81                                value.as_bool().unwrap().to_string()
82                            } else {
83                                value.as_str().unwrap().to_string()
84                            }
85                        };
86
87                        let mut context = context.clone();
88                        let mut rules_context = rules_context.clone();
89                        rules_context.set_break_triggered(false);
90                        let loop_context = LoopContext::new();
91                        context.insert("item", &item);
92                        context.insert("loop", &loop_context);
93
94                        let action: ActionId = self.actions().into();
95                        action.execute(
96                            archetect,
97                            archetype,
98                            destination.as_ref(),
99                            &mut rules_context,
100                            answers,
101                            &mut context,
102                        )?;
103                    }
104                }
105            }
106            ForEachSource::Split(options) => {
107                let input = archetect.render_string(&options.input, context)?;
108                let splits = input.split(&options.separator);
109
110                let mut context = context.clone();
111                let mut rules_context = rules_context.clone();
112                rules_context.set_break_triggered(false);
113
114                let mut loop_context = LoopContext::new();
115                for split in splits {
116                    if rules_context.break_triggered() {
117                        break;
118                    }
119                    let split = split.trim();
120                    if !split.is_empty() {
121                        context.insert("item", split);
122                        context.insert("loop", &loop_context);
123                        let action: ActionId = self.actions().into();
124                        action.execute(
125                            archetect,
126                            archetype,
127                            destination.as_ref(),
128                            &mut rules_context,
129                            answers,
130                            &mut context,
131                        )?;
132                        loop_context.increment();
133                    }
134                }
135            }
136        }
137        Ok(())
138    }
139}
140
141#[derive(Debug, Serialize, Deserialize, Clone)]
142pub struct ForAction {
143    #[serde(flatten)]
144    options: ForOptions,
145    #[serde(rename = "do")]
146    actions: Vec<ActionId>,
147}
148
149#[derive(Debug, Serialize, Deserialize, Clone)]
150pub enum ForOptions {
151    #[serde(rename = "item")]
152    Item {
153        #[serde(rename = "in")]
154        identifier: String,
155        name: Option<String>,
156        value: Option<String>,
157    },
158    #[serde(rename = "split")]
159    Split {
160        #[serde(rename = "in")]
161        input: String,
162        #[serde(rename = "sep")]
163        separator: Option<String>,
164        name: Option<String>,
165        value: Option<String>,
166    },
167}
168
169impl ForAction {
170    pub fn actions(&self) -> &Vec<ActionId> {
171        self.actions.as_ref()
172    }
173}
174
175impl Action for ForAction {
176    fn execute<D: AsRef<Path>>(
177        &self,
178        archetect: &mut Archetect,
179        archetype: &Archetype,
180        destination: D,
181        rules_context: &mut RulesContext,
182        answers: &LinkedHashMap<String, VariableInfo>,
183        context: &mut Context,
184    ) -> Result<(), ArchetectError> {
185        match &self.options {
186            ForOptions::Item {
187                identifier,
188                name,
189                value,
190            } => {
191                let format = value;
192                if let Some(value) = context.get(identifier) {
193                    if let Some(items) = value.as_array() {
194                        let mut context = context.clone();
195                        let mut rules_context = rules_context.clone();
196                        rules_context.set_break_triggered(false);
197
198                        let mut loop_context = LoopContext::new();
199
200                        for item in items {
201                            if rules_context.break_triggered() {
202                                break;
203                            }
204                            context.insert(name.clone().unwrap_or("item".to_owned()), item);
205                            if let Some(format) = format {
206                                let format = archetect.render_string(format, &context)?;
207                                context.insert(name.clone().unwrap_or("item".to_owned()), &format);
208                            }
209                            context.insert("loop", &loop_context);
210                            let action: ActionId = self.actions().into();
211                            action.execute(
212                                archetect,
213                                archetype,
214                                destination.as_ref(),
215                                &mut rules_context,
216                                answers,
217                                &mut context,
218                            )?;
219                            loop_context.increment();
220                        }
221                    } else {
222                        let item = {
223                            if value.is_number() {
224                                value.as_i64().unwrap().to_string()
225                            } else if value.is_boolean() {
226                                value.as_bool().unwrap().to_string()
227                            } else {
228                                value.as_str().unwrap().to_string()
229                            }
230                        };
231
232                        let mut context = context.clone();
233                        let mut rules_context = rules_context.clone();
234                        rules_context.set_break_triggered(false);
235                        let loop_context = LoopContext::new();
236                        context.insert(name.clone().unwrap_or("item".to_owned()).as_str(), &item);
237                        if let Some(format) = format {
238                            let format = archetect.render_string(format, &context)?;
239                            context.insert(name.clone().unwrap_or("item".to_owned()), &format);
240                        }
241                        context.insert("loop", &loop_context);
242
243                        let action: ActionId = self.actions().into();
244                        action.execute(
245                            archetect,
246                            archetype,
247                            destination.as_ref(),
248                            &mut rules_context,
249                            answers,
250                            &mut context,
251                        )?;
252                    }
253                }
254            }
255            ForOptions::Split {
256                input,
257                separator,
258                name,
259                value,
260            } => {
261                let format = value;
262                let input = archetect.render_string(input, context)?;
263                let separator = separator.clone().unwrap_or(",".to_owned());
264                let splits = input.split(&separator);
265
266                let mut context = context.clone();
267                let mut rules_context = rules_context.clone();
268                rules_context.set_break_triggered(false);
269
270                let mut loop_context = LoopContext::new();
271                for split in splits {
272                    if rules_context.break_triggered() {
273                        break;
274                    }
275                    let split = split.trim();
276                    if !split.is_empty() {
277                        context.insert(name.clone().unwrap_or("item".to_owned()).as_str(), split);
278                        if let Some(format) = format {
279                            let format = archetect.render_string(format, &context)?;
280                            context.insert(name.clone().unwrap_or("item".to_owned()), &format);
281                        }
282                        context.insert("loop", &loop_context);
283                        let action: ActionId = self.actions().into();
284                        action.execute(
285                            archetect,
286                            archetype,
287                            destination.as_ref(),
288                            &mut rules_context,
289                            answers,
290                            &mut context,
291                        )?;
292                        loop_context.increment();
293                    }
294                }
295            }
296        }
297        Ok(())
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use serde_yaml;
304
305    use crate::actions::foreach::{ForAction, ForOptions};
306    use crate::actions::ActionId;
307
308    #[test]
309    fn test_serialize_for_item() {
310        let action = ForAction {
311            options: ForOptions::Item {
312                identifier: "products".to_owned(),
313                name: Some("product".to_owned()),
314                value: None,
315            },
316            actions: vec![ActionId::Break],
317        };
318
319        println!("{}", serde_yaml::to_string(&action).unwrap());
320    }
321
322    #[test]
323    fn test_serialize_for_split() {
324        let action = ForAction {
325            options: ForOptions::Split {
326                input: "{{ products }}".to_owned(),
327                separator: Some(",".to_owned()),
328                name: Some("product".to_owned()),
329                value: None,
330            },
331            actions: vec![ActionId::Break],
332        };
333
334        println!("{}", serde_yaml::to_string(&action).unwrap());
335    }
336}