archetect_core/actions/
render.rs

1use std::path::Path;
2
3use linked_hash_map::LinkedHashMap;
4
5use crate::actions::{set, Action};
6use crate::config::AnswerInfo;
7use crate::rules::RulesContext;
8use crate::vendor::tera::Context;
9use crate::{Archetect, ArchetectError, Archetype};
10use std::fs;
11
12#[derive(Debug, Serialize, Deserialize, Clone)]
13pub enum RenderAction {
14    #[serde(rename = "directory")]
15    Directory(DirectoryOptions),
16    #[serde(rename = "archetype")]
17    Archetype(ArchetypeOptions),
18}
19
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub struct DirectoryOptions {
22    #[serde(skip_serializing_if = "Option::is_none")]
23    destination: Option<String>,
24    source: String,
25}
26
27impl DirectoryOptions {
28    pub fn new<S: Into<String>>(source: S) -> DirectoryOptions {
29        DirectoryOptions {
30            source: source.into(),
31            destination: None,
32        }
33    }
34
35    pub fn with_destination<D: Into<String>>(mut self, destination: D) -> DirectoryOptions {
36        self.destination = Some(destination.into());
37        self
38    }
39}
40
41#[derive(Debug, Serialize, Deserialize, Clone)]
42pub struct ArchetypeOptions {
43    #[serde(skip_serializing_if = "Option::is_none", rename = "inherit-answers", alias = "answers-include")]
44    answers_include: Option<Vec<String>>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    answers: Option<LinkedHashMap<String, AnswerInfo>>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    destination: Option<String>,
49    source: String,
50}
51
52impl ArchetypeOptions {
53    pub fn new<S: Into<String>>(source: S) -> ArchetypeOptions {
54        ArchetypeOptions {
55            answers_include: None,
56            answers: None,
57            source: source.into(),
58            destination: None,
59        }
60    }
61
62    pub fn with_destination<D: Into<String>>(mut self, destination: D) -> ArchetypeOptions {
63        self.destination = Some(destination.into());
64        self
65    }
66
67    pub fn with_inherited_answer(mut self, key: String) -> ArchetypeOptions {
68        self.answers_include.get_or_insert_with(|| Vec::new()).push(key);
69        self
70    }
71
72    pub fn with_answer(mut self, key: String, value: AnswerInfo) -> ArchetypeOptions {
73        self.answers.get_or_insert_with(|| LinkedHashMap::new()).insert(key, value);
74        self
75    }
76}
77
78impl Action for RenderAction {
79    fn execute<D: AsRef<Path>>(
80        &self,
81        archetect: &mut Archetect,
82        archetype: &Archetype,
83        destination: D,
84        rules_context: &mut RulesContext,
85        _answers: &LinkedHashMap<String, AnswerInfo>,
86        context: &mut Context,
87    ) -> Result<(), ArchetectError> {
88        match self {
89            RenderAction::Directory(options) => {
90                let source = archetype.source().directory().join(&options.source);
91                let destination = if let Some(dest) = &options.destination {
92                    if let Ok(result) = shellexpand::full(dest) {
93                        use log::debug;
94                        debug!("Archetype ShellExpand Dest: {}", result);
95                    }
96                    destination.as_ref().join(archetect.render_string(dest, context)?)
97                } else {
98                    destination.as_ref().to_owned()
99                };
100                fs::create_dir_all(destination.as_path())?;
101                archetect.render_directory(context, source, destination, rules_context)?;
102            }
103
104            RenderAction::Archetype(options) => {
105                let destination = if let Some(dest) = &options.destination {
106                    destination.as_ref().join(archetect.render_string(dest, context)?)
107                } else {
108                    destination.as_ref().to_owned()
109                };
110                let archetype = archetect.load_archetype(&options.source, Some(archetype.source().clone()))?;
111
112                let mut scoped_answers = LinkedHashMap::new();
113
114                if let Some(answers_include) = &options.answers_include {
115                    for identifier in answers_include {
116                        if let Some(value) = context.get(identifier) {
117                            if let Some(string) = value.as_str() {
118                                scoped_answers.insert(identifier.to_owned(), AnswerInfo::with_value(string).build());
119                            }
120                        }
121                    }
122                }
123
124                // Render any variables used in the definition of the AnswerInfo using the current
125                // context before sending the answers into the new Archetype, as it will start off
126                // with an empty context and unable to satisfy any variables.
127                if let Some(answers) = &options.answers {
128                    let rendered_answers = set::render_answers(archetect, answers, context)?;
129                    for (key, value) in rendered_answers {
130                        scoped_answers.insert(key, value);
131                    }
132                };
133
134                archetype.render(archetect, &destination, &scoped_answers)?;
135            }
136        }
137
138        Ok(())
139    }
140}