1use serde::{Deserialize, Serialize};
2
3use crate::env::match_program_with_envs;
4
5#[non_exhaustive]
6#[derive(Deserialize, Serialize, Debug)]
7pub struct CommandConfig {
8 pub program: String,
9 pub args: Option<Vec<String>>,
10 pub label: Option<String>,
11 pub envs: Option<Vec<(String, String)>>,
12 pub cwd: Option<String>,
13}
14
15#[non_exhaustive]
16#[derive(Debug, Default)]
17pub struct CommandConfigFromScriptOptions {
18 pub windows_call_cmd_with_env: super::WindowsCallCmdWithEnv,
19}
20
21macro_rules! def_into_command_and_label {
22 ($name:ident -> $cmd_type:ty) => {
23 pub fn $name<I, K, V>(self, inherited_envs: Option<I>) -> ($cmd_type, String)
24 where
25 I: IntoIterator<Item = (K, V)>,
26 K: AsRef<std::ffi::OsStr>,
27 V: AsRef<std::ffi::OsStr>,
28 {
29 let Self {
30 program,
31 args,
32 label,
33 envs,
34 cwd,
35 } = self;
36
37 let mut command = <$cmd_type>::new(&program);
38
39 if let Some(cwd) = cwd {
40 command.current_dir(cwd);
41 }
42
43 if let Some(args) = &args {
44 command.args(args);
45 }
46
47 if let Some(envs) = inherited_envs {
48 command.envs(envs);
49 }
50
51 if let Some(envs) = envs {
52 command.envs(envs);
53 }
54
55 let label = label.unwrap_or_else(move || {
56 if let Some(args) = args {
57 format!("{} {}", program, args.join(" "))
58 } else {
59 program
60 }
61 });
62
63 (command, label)
64 }
65 };
66}
67
68impl CommandConfig {
69 pub fn from_script(script: &str, options: &CommandConfigFromScriptOptions) -> CommandConfig {
70 let script = script.trim();
71
72 let (program, envs) = match_program_with_envs(script);
73
74 let program = program.to_string();
75 let envs = if let Some(envs) = envs {
76 Some(
77 envs.into_iter()
78 .map(|(k, v)| (k.to_string(), v.to_string()))
79 .collect(),
80 )
81 } else {
82 None
83 };
84
85 if cfg!(target_os = "windows") {
86 let with_env = &options.windows_call_cmd_with_env;
87
88 let env_name = with_env.clone().try_into_env_name();
89
90 let (arg, env) = match env_name {
91 Some(env_name) => (format!("%{}%", env_name), Some((env_name, program.clone()))),
92 None => (program.clone(), None),
93 };
94
95 let mut cmd = CommandConfig {
96 program: "cmd".to_string(),
97 args: Some(vec!["/C".to_string(), arg]),
98 label: Some(program.clone()),
99 envs,
100 cwd: None,
101 };
102
103 if let Some(env) = env {
104 cmd.env(env);
105 }
106
107 cmd
108 } else {
109 CommandConfig {
110 program: "sh".to_string(),
111 args: Some(vec!["-c".to_string(), program.clone()]),
112 label: Some(program),
113 envs,
114 cwd: None,
115 }
116 }
117 }
118
119 pub fn from_program_args(program: String, args: Option<Vec<String>>) -> CommandConfig {
120 CommandConfig {
121 program,
122 args,
123 label: None,
124 envs: None,
125 cwd: None,
126 }
127 }
128
129 pub fn label_length(&self) -> usize {
130 match &self.label {
131 None => {
132 self.program.len()
133 + self
134 .args
135 .as_ref()
136 .map_or(0, |args| args.iter().map(|arg| arg.len() + 1).sum())
137 }
138 Some(label) => label.len(),
139 }
140 }
141
142 pub fn env(&mut self, env: (String, String)) -> &mut Self {
143 self.envs.get_or_insert_with(|| vec![]).push(env);
144 self
145 }
146
147 def_into_command_and_label! {into_command_and_label->std::process::Command}
148
149 def_into_command_and_label! {into_tokio_command_and_label->tokio::process::Command}
150}