archetect_core/actions/
exec.rs

1use std::collections::hash_map::RandomState;
2use std::path::Path;
3use std::process::Command;
4
5use linked_hash_map::LinkedHashMap;
6use log::{debug, warn};
7
8use crate::actions::Action;
9use crate::config::VariableInfo;
10use crate::rules::RulesContext;
11use crate::{Archetect, ArchetectError, Archetype};
12use crate::vendor::tera::Context;
13
14#[derive(Debug, Serialize, Deserialize, Clone)]
15pub struct ExecAction {
16    command: String,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    args: Option<Vec<String>>,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    env: Option<LinkedHashMap<String, String>>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    cwd: Option<String>,
23}
24
25impl ExecAction {
26    pub fn new<C: Into<String>>(command: C) -> ExecAction {
27        ExecAction {
28            command: command.into(),
29            args: None,
30            env: None,
31            cwd: None,
32        }
33    }
34
35    pub fn args(&self) -> Option<&Vec<String>> {
36        self.args.as_ref()
37    }
38
39    pub fn with_arg<A: Into<String>>(mut self, arg: A) -> ExecAction {
40        self.add_arg(arg);
41        self
42    }
43
44    pub fn add_arg<A: Into<String>>(&mut self, arg: A) {
45        let args = self.args.get_or_insert_with(|| Default::default());
46        args.push(arg.into());
47    }
48
49    pub fn env(&self) -> Option<&LinkedHashMap<String, String>> {
50        self.env.as_ref()
51    }
52
53    pub fn with_environment_variable<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> ExecAction {
54        self.add_environment_variable(key, value);
55        self
56    }
57
58    pub fn add_environment_variable<K: Into<String>, V: Into<String>>(&mut self, key: K, value: V) {
59        let env = self.env.get_or_insert_with(|| Default::default());
60        env.insert(key.into(), value.into());
61    }
62
63    pub fn cwd(&self) -> Option<&String> {
64        self.cwd.as_ref()
65    }
66
67    pub fn with_working_directory<D: Into<String>>(mut self, directory: D) -> ExecAction {
68        self.set_working_directory(directory);
69        self
70    }
71
72    pub fn set_working_directory<D: Into<String>>(&mut self, directory: D) {
73        self.cwd = Some(directory.into());
74    }
75}
76
77impl Action for ExecAction {
78    fn execute<D: AsRef<Path>>(
79        &self,
80        archetect: &mut Archetect,
81        _archetype: &Archetype,
82        destination: D,
83        _rules_context: &mut RulesContext,
84        _answers: &LinkedHashMap<String, VariableInfo, RandomState>,
85        context: &mut Context,
86    ) -> Result<(), ArchetectError> {
87        let mut command = Command::new(&self.command);
88
89        if let Some(args) = self.args() {
90            for arg in args {
91                command.arg(archetect.render_string(arg, context)?);
92            }
93        }
94
95        if let Some(env) = self.env() {
96            for (key, value) in env {
97                command.env(
98                    archetect.render_string(key, context)?,
99                    archetect.render_string(value, context)?,
100                );
101            }
102        }
103
104        if let Some(cwd) = &self.cwd {
105            if let Ok(cwd) = shellexpand::full(cwd) {
106                let cwd = Path::new(cwd.as_ref());
107                if cwd.is_relative() {
108                    command.current_dir(
109                        destination
110                            .as_ref()
111                            .join(archetect.render_string(cwd.display().to_string().as_str(), context)?),
112                    );
113                } else {
114                    command.current_dir(archetect.render_string(cwd.display().to_string().as_str(), context)?);
115                }
116            }
117        } else {
118            command.current_dir(destination);
119        }
120
121        debug!("[exec] Executing: {:?}", command);
122        match command.status() {
123            Ok(status) => {
124                debug!("[exec] Status: {}", status.code().unwrap());
125            }
126            Err(error) => {
127                warn!("[exec] Error: {}", error);
128            }
129        }
130
131        Ok(())
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use crate::actions::exec::ExecAction;
138    use linked_hash_map::LinkedHashMap;
139    use serde_yaml;
140
141    #[test]
142    fn test_serialize() {
143        let mut env = LinkedHashMap::new();
144        env.insert("M2_HOME".to_owned(), "~/.m2".to_owned());
145        env.insert("MAVEN_HOME".to_owned(), "/usr/bin".to_owned());
146
147        let mut foo = LinkedHashMap::new();
148        foo.insert("exmple".to_owned(), ());
149        let action = ExecAction {
150            command: "mvn".to_string(),
151            args: Some(vec!["install".to_owned()]),
152            env: Some(env),
153            cwd: None,
154        };
155
156        println!("{}", serde_yaml::to_string(&action).unwrap());
157    }
158}