1use crate::{constants, display, errors, eval, model, syntax};
2
3pub fn status(exec: subprocess::Exec) -> u32 {
5 if let Err(status) = subprocess_result(exec.join()) {
6 status
7 } else {
8 errors::EX_OK
9 }
10}
11
12pub fn subprocess_result(result: std::io::Result<subprocess::ExitStatus>) -> Result<(), u32> {
14 match result {
15 Ok(status) => {
16 let status_code = exit_status(status);
17 if status_code == 0 {
18 Ok(())
19 } else {
20 Err(status_code)
21 }
22 }
23 Err(err) => match err.kind() {
24 std::io::ErrorKind::NotFound => Err(errors::EX_UNAVAILABLE),
25 std::io::ErrorKind::PermissionDenied => Err(errors::EX_IOERR),
26 _ => Err(errors::EX_ERROR),
27 },
28 }
29}
30
31fn stdout(capture: &subprocess::Capture) -> String {
33 capture.stdout_str().trim_end().to_string()
34}
35
36pub(crate) fn capture_stdout(
38 exec: subprocess::Exec,
39) -> Result<subprocess::Capture, errors::CommandError> {
40 let command = exec.to_cmdline_lossy();
41 let capture = exec
42 .stderr(subprocess::Redirection::Null)
43 .stdout(subprocess::Redirection::Pipe)
44 .capture();
45
46 match capture {
47 Ok(result) => {
48 let status = exit_status(result.exit_status);
49 if status == 0 {
50 Ok(result)
51 } else {
52 Err(errors::CommandError::ExitStatus { command, status })
53 }
54 }
55 Err(_) => Err(errors::CommandError::ExitStatus {
56 command,
57 status: errors::EX_ERROR,
58 }),
59 }
60}
61
62pub(crate) fn exit_status(status: subprocess::ExitStatus) -> u32 {
64 status.code().unwrap_or(errors::EX_ERROR)
65}
66
67pub fn stdout_to_string(exec: subprocess::Exec) -> Result<String, errors::CommandError> {
69 Ok(stdout(&capture_stdout(exec)?))
70}
71
72pub fn exec_cmd<S>(command: &[S]) -> subprocess::Exec
74where
75 S: AsRef<std::ffi::OsStr>,
76{
77 if command.len() > 1 {
78 subprocess::Exec::cmd(&command[0]).args(&command[1..])
79 } else {
80 subprocess::Exec::cmd(&command[0])
81 }
82}
83
84pub fn exec_in_dir<P, S>(command: &[S], path: &P) -> subprocess::Exec
86where
87 P: AsRef<std::path::Path> + std::convert::AsRef<std::ffi::OsStr> + ?Sized,
88 S: AsRef<std::ffi::OsStr>,
89{
90 exec_cmd(command).cwd(path).env(constants::ENV_PWD, path)
91}
92
93pub(crate) fn run_command<P, S>(command: &[S], path: &P) -> u32
96where
97 P: AsRef<std::path::Path> + std::convert::AsRef<std::ffi::OsStr> + ?Sized,
98 S: AsRef<std::ffi::OsStr>,
99{
100 status(exec_in_dir(command, path))
101}
102
103pub(crate) fn exec_in_context<S>(
111 app_context: &model::ApplicationContext,
112 config: &model::Configuration,
113 context: &model::TreeContext,
114 quiet: bool,
115 verbose: u8,
116 dry_run: bool,
117 command: &[S],
118) -> Result<(), errors::GardenError>
119where
120 S: AsRef<std::ffi::OsStr>,
121{
122 let display_options = display::DisplayOptions {
123 branches: config.tree_branches,
124 verbose,
125 quiet,
126 ..std::default::Default::default()
127 };
128 let graft_config = context
129 .config
130 .map(|graft_id| app_context.get_config(graft_id));
131
132 let path;
133 if let Some(graft_cfg) = graft_config {
134 if let Some(tree) = graft_cfg.trees.get(&context.tree) {
135 path = tree.path_as_ref()?;
136
137 if !display::print_tree(tree, &display_options) {
139 return Ok(());
140 }
141 } else {
142 return Ok(());
143 }
144 } else if let Some(tree) = config.trees.get(&context.tree) {
145 path = tree.path_as_ref()?;
146
147 if !display::print_tree(tree, &display_options) {
149 return Ok(());
150 }
151 } else {
152 return Ok(());
153 }
154 let env = eval::environment(app_context, config, context);
156 let command_vec = resolve_command(command, &env);
157 if verbose > 1 || dry_run {
158 display::print_command_string_vec(&command_vec);
159 }
160 if dry_run {
161 return Ok(());
162 }
163
164 let mut exec = exec_in_dir(&command_vec, &path);
166
167 for (name, value) in &env {
169 exec = exec.env(name, value);
170 }
171
172 errors::result_from_exit_status(status(exec))
173}
174
175fn resolve_command<S>(command: &[S], env: &[(String, String)]) -> Vec<String>
179where
180 S: AsRef<std::ffi::OsStr>,
181{
182 let mut cmd_path = std::path::PathBuf::from(&command[0]);
183 if !cmd_path.is_absolute() {
185 for (name, value) in env {
186 if name == constants::ENV_PATH {
188 if let Some(path_buf) = std::env::split_paths(&value).find_map(|dir| {
189 let full_path = dir.join(&cmd_path);
190 if full_path.is_file() {
191 Some(full_path)
192 } else {
193 None
194 }
195 }) {
196 cmd_path = path_buf;
197 }
198 break;
200 }
201 }
202 }
203
204 let mut command_vec = Vec::with_capacity(command.len());
207 command_vec.push(cmd_path.to_string_lossy().to_string());
208 for arg in &command[1..] {
209 let curpath = std::path::PathBuf::from(arg);
210 command_vec.push(curpath.to_string_lossy().into());
211 }
212
213 command_vec
214}
215
216pub(crate) fn current_exe() -> String {
218 match std::env::current_exe() {
219 Err(_) => constants::GARDEN.into(),
220 Ok(path) => path.to_string_lossy().into(),
221 }
222}
223
224pub(crate) fn get_command_values(
228 app_context: &model::ApplicationContext,
229 context: &model::TreeContext,
230 name: &str,
231) -> Vec<String> {
232 let config = match context.config {
233 Some(config_id) => app_context.get_config(config_id),
234 None => app_context.get_root_config(),
235 };
236 let mut vec_variables = Vec::new();
237
238 for (command_name, var) in &config.commands {
240 if name == command_name {
241 vec_variables.push(var.clone());
242 }
243 }
244
245 if let Some(tree) = config.trees.get(&context.tree) {
247 for (command_name, var) in &tree.commands {
248 if name == command_name {
249 vec_variables.push(var.clone());
250 }
251 }
252 }
253
254 if let Some(garden_name) = &context.garden {
256 if let Some(garden) = &config.gardens.get(garden_name) {
257 for (command_name, var) in &garden.commands {
258 if name == command_name {
259 vec_variables.push(var.clone());
260 }
261 }
262 }
263 }
264
265 let mut commands = Vec::with_capacity(vec_variables.len() * 2);
266 for variables in vec_variables.iter_mut() {
267 let values = eval::variables_for_shell(app_context, config, variables, context);
268 commands.extend(values);
269 }
270
271 commands
272}
273
274pub(crate) fn expand_command_names(
277 app_context: &model::ApplicationContext,
278 context: &model::TreeContext,
279 name: &str,
280) -> Vec<String> {
281 let pre_name = syntax::pre_command(name);
282 let post_name = syntax::post_command(name);
283 let pre_commands = get_command_values(app_context, context, &pre_name);
284 let post_commands = get_command_values(app_context, context, &post_name);
285
286 let mut command_names = Vec::with_capacity(pre_commands.len() + 1 + post_commands.len());
287 for cmd_name in pre_commands.iter() {
289 if cmd_name != name {
290 command_names.extend(expand_command_names(app_context, context, cmd_name));
292 }
293 }
294 command_names.push(name.to_string());
295 for cmd_name in post_commands.iter() {
297 if cmd_name != name {
298 command_names.extend(expand_command_names(app_context, context, cmd_name));
300 }
301 }
302
303 command_names
304}
305
306pub(crate) fn shell_quote(arg: &str) -> String {
309 shlex::try_quote(arg)
310 .map(|quoted_arg| quoted_arg.to_string())
311 .unwrap_or_else(|_| arg.to_string())
312}
313
314pub fn shlex_split(shell: &str) -> Vec<String> {
316 if shell.is_empty() {
317 return Vec::new();
318 }
319 match shlex::split(shell) {
320 Some(shell_command) if !shell_command.is_empty() => shell_command,
321 _ => {
322 vec![shell.to_string()]
323 }
324 }
325}
326
327pub(crate) fn default_num_jobs() -> usize {
329 match std::thread::available_parallelism() {
330 Ok(value) => std::cmp::max(value.get(), 3), Err(_) => 4,
332 }
333}
334
335pub(crate) fn initialize_threads(num_jobs: usize) -> anyhow::Result<()> {
337 let num_jobs = if num_jobs == 0 {
338 default_num_jobs()
339 } else {
340 num_jobs
341 };
342 rayon::ThreadPoolBuilder::new()
343 .num_threads(num_jobs)
344 .build_global()?;
345
346 Ok(())
347}
348
349pub fn initialize_threads_option(num_jobs: Option<usize>) -> anyhow::Result<()> {
351 let Some(num_jobs_value) = num_jobs else {
352 return Ok(());
353 };
354
355 initialize_threads(num_jobs_value)
356}