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}