archetect_core/
actions.rs

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    // Output
52    #[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            // Logging
96            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}