1use std::path::Path;
2
3use linked_hash_map::LinkedHashMap;
4use log::{debug, error, info, trace, warn};
5
6use crate::actions::conditionals::IfAction;
7use crate::actions::exec::ExecAction;
8use crate::actions::foreach::{ForAction, ForEachAction};
9use crate::actions::render::RenderAction;
10use crate::actions::rules::RuleType;
11use crate::config::{AnswerInfo, VariableInfo};
12use crate::rendering::Renderable;
13use crate::rules::RulesContext;
14use crate::{Archetect, ArchetectError, Archetype};
15use crate::vendor::tera::Context;
16
17pub mod conditionals;
18pub mod exec;
19pub mod foreach;
20pub mod load;
21pub mod render;
22pub mod rules;
23pub mod set;
24
25#[derive(Debug, Serialize, Deserialize, Clone)]
26pub enum ActionId {
27 #[serde(rename = "set")]
28 Set(LinkedHashMap<String, VariableInfo>),
29 #[serde(rename = "scope")]
30 Scope(Vec<ActionId>),
31 #[serde(rename = "actions")]
32 Actions(Vec<ActionId>),
33 #[serde(rename = "render")]
34 Render(RenderAction),
35 #[serde(rename = "for-each")]
36 ForEach(ForEachAction),
37 #[serde(rename = "for")]
38 For(ForAction),
39 #[serde(rename = "loop")]
40 Loop(Vec<ActionId>),
41 #[serde(rename = "break")]
42 Break,
43 #[serde(rename = "if")]
44 If(IfAction),
45 #[serde(rename = "rules")]
46 Rules(Vec<RuleType>),
47
48 #[serde(rename = "exec")]
49 Exec(ExecAction),
50
51 #[serde(rename = "trace")]
53 LogTrace(String),
54 #[serde(rename = "debug")]
55 LogDebug(String),
56 #[serde(rename = "info")]
57 LogInfo(String),
58 #[serde(rename = "warn")]
59 LogWarn(String),
60 #[serde(rename = "error")]
61 LogError(String),
62 #[serde(rename = "print")]
63 Print(String),
64 #[serde(rename = "display")]
65 Display(String),
66}
67
68impl ActionId {
69 pub fn execute<D: AsRef<Path>>(
70 &self,
71 archetect: &mut Archetect,
72 archetype: &Archetype,
73 destination: D,
74 rules_context: &mut RulesContext,
75 answers: &LinkedHashMap<String, AnswerInfo>,
76 context: &mut Context,
77 ) -> Result<(), ArchetectError> {
78 let destination = destination.as_ref();
79 match self {
80 ActionId::Set(variables) => {
81 set::populate_context(archetect, variables, answers, context)?;
82 }
83 ActionId::Render(action) => {
84 action.execute(archetect, archetype, destination, rules_context, answers, context)?
85 }
86 ActionId::Actions(action_ids) => {
87 for action_id in action_ids {
88 action_id.execute(archetect, archetype, destination, rules_context, answers, context)?;
89 if rules_context.break_triggered() {
90 break;
91 }
92 }
93 }
94
95 ActionId::LogTrace(message) => trace!("{}", message.render(archetect, context)?),
97 ActionId::LogDebug(message) => debug!("{}", message.render(archetect, context)?),
98 ActionId::LogInfo(message) => info!("{}", message.render(archetect, context)?),
99 ActionId::LogWarn(message) => warn!("{}", message.render(archetect, context)?),
100 ActionId::LogError(message) => error!("{}", message.render(archetect, context)?),
101 ActionId::Print(message) => println!("{}", message.render(archetect, context)?),
102 ActionId::Display(message) => eprintln!("{}", message.render(archetect, context)?),
103
104 ActionId::Scope(actions) => {
105 let mut rules_context = rules_context.clone();
106 let mut scope_context = context.clone();
107 let action: ActionId = actions.into();
108 action.execute(
109 archetect,
110 archetype,
111 destination,
112 &mut rules_context,
113 answers,
114 &mut scope_context,
115 )?;
116 }
117 ActionId::If(action) => {
118 action.execute(archetect, archetype, destination, rules_context, answers, context)?
119 }
120 ActionId::Rules(actions) => {
121 for action in actions {
122 action.execute(archetect, archetype, destination, rules_context, answers, context)?;
123 }
124 }
125 ActionId::ForEach(action) => {
126 action.execute(archetect, archetype, destination, rules_context, answers, context)?;
127 }
128 ActionId::For(action) => {
129 action.execute(archetect, archetype, destination, rules_context, answers, context)?;
130 }
131
132 ActionId::Loop(actions) => {
133 let mut context = context.clone();
134 let mut rules_context = rules_context.clone();
135 rules_context.set_break_triggered(false);
136
137 let mut loop_context = LoopContext::new();
138 while !rules_context.break_triggered() {
139 context.insert("loop", &loop_context);
140 let action: ActionId = actions[..].into();
141 action.execute(
142 archetect,
143 archetype,
144 destination,
145 &mut rules_context,
146 answers,
147 &mut context,
148 )?;
149 loop_context.increment();
150 }
151 }
152 ActionId::Break => {
153 rules_context.set_break_triggered(true);
154 }
155 ActionId::Exec(action) => {
156 action.execute(archetect, archetype, destination, rules_context, answers, context)?;
157 }
158 }
159
160 Ok(())
161 }
162}
163
164#[derive(Debug, Serialize, Deserialize)]
165pub struct LoopContext {
166 index: i32,
167 index0: i32,
168}
169
170impl LoopContext {
171 fn new() -> LoopContext {
172 LoopContext { index: 1, index0: 0 }
173 }
174
175 fn increment(&mut self) {
176 self.index = self.index + 1;
177 self.index0 = self.index0 + 1;
178 }
179}
180
181impl From<Vec<ActionId>> for ActionId {
182 fn from(action_ids: Vec<ActionId>) -> Self {
183 ActionId::Actions(action_ids)
184 }
185}
186
187impl From<&[ActionId]> for ActionId {
188 fn from(action_ids: &[ActionId]) -> Self {
189 let actions: Vec<ActionId> = action_ids.iter().map(|i| i.to_owned()).collect();
190 ActionId::Actions(actions)
191 }
192}
193
194impl From<&Vec<ActionId>> for ActionId {
195 fn from(action_ids: &Vec<ActionId>) -> Self {
196 let actions: Vec<ActionId> = action_ids.iter().map(|i| i.to_owned()).collect();
197 ActionId::Actions(actions)
198 }
199}
200
201pub trait Action {
202 fn execute<D: AsRef<Path>>(
203 &self,
204 archetect: &mut Archetect,
205 archetype: &Archetype,
206 destination: D,
207 rules_context: &mut RulesContext,
208 answers: &LinkedHashMap<String, AnswerInfo>,
209 context: &mut Context,
210 ) -> Result<(), ArchetectError>;
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use indoc::indoc;
217 use crate::utils::testing::{strip_newline};
218 use crate::actions::render::{ArchetypeOptions, DirectoryOptions};
219
220 #[test]
221 fn test_serialize() {
222 let actions = vec![
223 ActionId::LogWarn("Warning!!".to_owned()),
224
225 ActionId::Render(RenderAction::Directory(DirectoryOptions::new("."))),
226 ActionId::Render(RenderAction::Archetype(ArchetypeOptions::new(
227 "git@github.com:archetect/archetype-rust-cli.git",)
228 .with_inherited_answer("fname".into())
229 .with_answer("lname".into(), AnswerInfo::with_value("Brown").build())
230 )),
231 ];
232
233 let yaml = serde_yaml::to_string(&actions).unwrap();
234 println!("{}", yaml);
235
236 let expected = indoc! {r#"
237 ---
238 - warn: Warning!!
239 - render:
240 directory:
241 source: "."
242 - render:
243 archetype:
244 inherit-answers:
245 - fname
246 answers:
247 lname:
248 value: Brown
249 source: "git@github.com:archetect/archetype-rust-cli.git""#};
250 assert_eq!(strip_newline(&yaml), strip_newline(expected));
251 }
252}