cli/scriptengine/
mod.rs

1//! # scriptengine
2//!
3//! Facade for all different non OS scripts.
4//!
5
6pub(crate) mod duck_script;
7pub(crate) mod generic_script;
8mod os_script;
9mod rsscript;
10pub(crate) mod script_utils;
11mod shebang_script;
12mod shell_to_batch;
13
14#[cfg(test)]
15#[path = "mod_test.rs"]
16mod mod_test;
17
18use crate::environment;
19use crate::error::CargoMakeError;
20use crate::io;
21use crate::toolchain;
22use crate::types::{FlowInfo, FlowState, ScriptValue, Task};
23use std::cell::RefCell;
24use std::path::PathBuf;
25use std::rc::Rc;
26
27#[derive(Debug, Clone, PartialEq)]
28/// The currently supported engine types
29pub(crate) enum EngineType {
30    /// OS native script
31    OS,
32    /// Duckscript script runner
33    Duckscript,
34    /// Rust language
35    Rust,
36    /// shell to windows batch conversion
37    Shell2Batch,
38    /// Generic script runner
39    Generic,
40    /// Shebang script runner
41    Shebang,
42    /// Unsupported type
43    Unsupported,
44}
45
46pub(crate) fn get_script_text(script: &ScriptValue) -> Result<Vec<String>, CargoMakeError> {
47    match script {
48        ScriptValue::SingleLine(text) => Ok(vec![text.clone()]),
49        ScriptValue::Text(text) => Ok(text.clone()),
50        ScriptValue::File(info) => {
51            let mut file_path_string = String::new();
52            if !info.absolute_path.unwrap_or(false) {
53                file_path_string.push_str("${CARGO_MAKE_WORKING_DIRECTORY}/");
54            }
55            file_path_string.push_str(&info.file);
56
57            // expand env
58            let expanded_value = environment::expand_value(&file_path_string);
59
60            let mut file_path = PathBuf::new();
61            file_path.push(expanded_value);
62
63            let script_text = io::read_text_file(&file_path)?;
64            let lines: Vec<&str> = script_text.split('\n').collect();
65
66            let mut script_lines: Vec<String> = vec![];
67
68            for line in lines.iter() {
69                script_lines.push(line.to_string());
70            }
71
72            Ok(script_lines)
73        }
74        ScriptValue::Sections(sections) => {
75            let mut script_lines = vec![];
76
77            if let Some(ref text) = sections.pre {
78                script_lines.push(text.to_string());
79            }
80            if let Some(ref text) = sections.main {
81                script_lines.push(text.to_string());
82            }
83            if let Some(ref text) = sections.post {
84                script_lines.push(text.to_string());
85            }
86
87            Ok(script_lines)
88        }
89    }
90}
91
92fn get_internal_runner(script_runner: &str) -> EngineType {
93    if script_runner == "@duckscript" {
94        debug!("Duckscript detected.");
95        EngineType::Duckscript
96    } else if script_runner == "@rust" {
97        debug!("Rust script detected.");
98        EngineType::Rust
99    } else if script_runner == "@shell" {
100        debug!("Shell to batch detected.");
101        EngineType::Shell2Batch
102    } else {
103        EngineType::Unsupported
104    }
105}
106
107pub(crate) fn get_engine_type(
108    script: &ScriptValue,
109    script_runner: &Option<String>,
110    script_extension: &Option<String>,
111) -> Result<EngineType, CargoMakeError> {
112    match script_runner {
113        Some(ref runner) => {
114            debug!("Checking script runner: {}", runner);
115
116            let engine_type = get_internal_runner(runner);
117
118            match engine_type {
119                EngineType::Unsupported => {
120                    if script_extension.is_some() {
121                        // if both script runner and extension is defined, we use generic script runner
122                        debug!("Generic script detected.");
123                        Ok(EngineType::Generic)
124                    } else {
125                        // use default OS extension with custom runner
126                        debug!("OS script with custom runner detected.");
127                        Ok(EngineType::OS)
128                    }
129                }
130                _ => Ok(engine_type),
131            }
132        }
133        None => {
134            // if no runner specified, try to extract it from script content
135            let script_text = get_script_text(&script)?;
136
137            let shebang = shebang_script::get_shebang(&script_text);
138            match shebang.runner {
139                Some(script_runner) => {
140                    if shebang.arguments.is_none() {
141                        let engine_type = get_internal_runner(&script_runner);
142
143                        match engine_type {
144                            EngineType::Unsupported => {
145                                debug!("Shebang line does not point to an internal engine, using normal shebang script runner.");
146                                Ok(EngineType::Shebang)
147                            }
148                            _ => Ok(engine_type),
149                        }
150                    } else {
151                        Ok(EngineType::Shebang)
152                    }
153                }
154                None => Ok(EngineType::OS),
155            }
156        }
157    }
158}
159
160pub(crate) fn invoke(
161    task: &Task,
162    flow_info: &FlowInfo,
163    flow_state: Rc<RefCell<FlowState>>,
164) -> Result<bool, CargoMakeError> {
165    match task.script {
166        Some(ref script) => {
167            let validate = !task.should_ignore_errors();
168
169            // setup toolchain environment
170            let (reset_env, original_cargo) = match task.toolchain {
171                Some(ref toolchain) => match toolchain::get_cargo_binary_path(toolchain) {
172                    Some(cargo_binary) => (true, envmnt::get_set("CARGO", cargo_binary)),
173                    None => (false, None),
174                },
175                None => (false, None),
176            };
177
178            let output = invoke_script_in_flow_context(
179                script,
180                task.script_runner.clone(),
181                task.script_runner_args.clone(),
182                task.script_extension.clone(),
183                validate,
184                Some(flow_info),
185                Some(flow_state),
186            );
187
188            // reset toolchain environment
189            if reset_env {
190                if let Some(value) = original_cargo {
191                    envmnt::set("CARGO", value)
192                }
193            }
194
195            output
196        }
197        None => Ok(false),
198    }
199}
200
201pub(crate) fn invoke_script_in_flow_context(
202    script: &ScriptValue,
203    script_runner: Option<String>,
204    script_runner_args: Option<Vec<String>>,
205    script_extension: Option<String>,
206    validate: bool,
207    flow_info: Option<&FlowInfo>,
208    flow_state: Option<Rc<RefCell<FlowState>>>,
209) -> Result<bool, CargoMakeError> {
210    let cli_arguments = match flow_info {
211        Some(info) => match info.cli_arguments {
212            Some(ref args) => args.clone(),
213            None => vec![],
214        },
215        None => vec![],
216    };
217
218    invoke_script(
219        script,
220        script_runner,
221        script_runner_args,
222        script_extension,
223        validate,
224        flow_info,
225        flow_state,
226        &cli_arguments,
227    )
228}
229
230pub(crate) fn invoke_script_pre_flow(
231    script: &ScriptValue,
232    script_runner: Option<String>,
233    script_runner_args: Option<Vec<String>>,
234    script_extension: Option<String>,
235    validate: bool,
236    cli_arguments: &Vec<String>,
237) -> Result<bool, CargoMakeError> {
238    invoke_script(
239        script,
240        script_runner,
241        script_runner_args,
242        script_extension,
243        validate,
244        None,
245        None,
246        cli_arguments,
247    )
248}
249
250fn invoke_script(
251    script: &ScriptValue,
252    script_runner: Option<String>,
253    script_runner_args: Option<Vec<String>>,
254    script_extension: Option<String>,
255    validate: bool,
256    flow_info: Option<&FlowInfo>,
257    flow_state: Option<Rc<RefCell<FlowState>>>,
258    cli_arguments: &Vec<String>,
259) -> Result<bool, CargoMakeError> {
260    let expanded_script_runner = match script_runner {
261        Some(ref value) => Some(environment::expand_value(value)),
262        None => None,
263    };
264    let engine_type = get_engine_type(script, &expanded_script_runner, &script_extension)?;
265
266    match engine_type {
267        EngineType::OS => {
268            let script_text = get_script_text(script)?;
269            os_script::execute(
270                &script_text,
271                expanded_script_runner,
272                cli_arguments,
273                validate,
274            )
275        }
276        EngineType::Duckscript => {
277            let script_text = get_script_text(script)?;
278            duck_script::execute(&script_text, cli_arguments, flow_info, flow_state, validate)
279        }
280        EngineType::Rust => {
281            let script_text = get_script_text(script)?;
282            rsscript::execute(
283                &script_text,
284                script_runner_args.clone(),
285                cli_arguments,
286                validate,
287            )
288        }
289        EngineType::Shell2Batch => {
290            let script_text = get_script_text(script)?;
291            shell_to_batch::execute(&script_text, cli_arguments, validate)
292        }
293        EngineType::Generic => {
294            let script_text = get_script_text(script)?;
295            let extension = script_extension.clone().unwrap();
296            generic_script::execute(
297                &script_text,
298                expanded_script_runner.unwrap(),
299                extension,
300                script_runner_args.clone(),
301                cli_arguments,
302                validate,
303            )
304        }
305        EngineType::Shebang => {
306            let script_text = get_script_text(script)?;
307            let extension = script_extension.clone();
308            shebang_script::execute(&script_text, &extension, cli_arguments, validate)
309        }
310        EngineType::Unsupported => Ok(false),
311    }
312}