unistore_process/
command.rs

1//! 命令构建器
2//!
3//! 职责:提供流畅的命令构建 API
4
5use crate::child::Child;
6use crate::error::ProcessError;
7use crate::output::ProcessOutput;
8use std::collections::HashMap;
9use std::path::PathBuf;
10use tokio::process::Command as TokioCommand;
11
12/// 命令构建器
13pub struct Command {
14    program: String,
15    args: Vec<String>,
16    envs: HashMap<String, String>,
17    env_clear: bool,
18    current_dir: Option<PathBuf>,
19    stdin_piped: bool,
20    stdout_piped: bool,
21    stderr_piped: bool,
22}
23
24impl Command {
25    /// 创建新命令
26    pub fn new(program: impl Into<String>) -> Self {
27        Self {
28            program: program.into(),
29            args: Vec::new(),
30            envs: HashMap::new(),
31            env_clear: false,
32            current_dir: None,
33            stdin_piped: false,
34            stdout_piped: true,
35            stderr_piped: true,
36        }
37    }
38
39    /// 创建构建器
40    pub fn builder(program: impl Into<String>) -> CommandBuilder {
41        CommandBuilder::new(program)
42    }
43
44    /// 添加参数
45    pub fn arg(mut self, arg: impl Into<String>) -> Self {
46        self.args.push(arg.into());
47        self
48    }
49
50    /// 添加多个参数
51    pub fn args<I, S>(mut self, args: I) -> Self
52    where
53        I: IntoIterator<Item = S>,
54        S: Into<String>,
55    {
56        self.args.extend(args.into_iter().map(|s| s.into()));
57        self
58    }
59
60    /// 设置环境变量
61    pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
62        self.envs.insert(key.into(), value.into());
63        self
64    }
65
66    /// 设置多个环境变量
67    pub fn envs<I, K, V>(mut self, vars: I) -> Self
68    where
69        I: IntoIterator<Item = (K, V)>,
70        K: Into<String>,
71        V: Into<String>,
72    {
73        self.envs
74            .extend(vars.into_iter().map(|(k, v)| (k.into(), v.into())));
75        self
76    }
77
78    /// 清除所有环境变量
79    pub fn env_clear(mut self) -> Self {
80        self.env_clear = true;
81        self
82    }
83
84    /// 设置工作目录
85    pub fn current_dir(mut self, dir: impl Into<PathBuf>) -> Self {
86        self.current_dir = Some(dir.into());
87        self
88    }
89
90    /// 捕获标准输入
91    pub fn stdin_piped(mut self) -> Self {
92        self.stdin_piped = true;
93        self
94    }
95
96    /// 不捕获标准输出
97    pub fn stdout_inherit(mut self) -> Self {
98        self.stdout_piped = false;
99        self
100    }
101
102    /// 不捕获标准错误
103    pub fn stderr_inherit(mut self) -> Self {
104        self.stderr_piped = false;
105        self
106    }
107
108    /// 构建 tokio Command
109    fn build_command(&self) -> TokioCommand {
110        let mut cmd = TokioCommand::new(&self.program);
111
112        cmd.args(&self.args);
113
114        if self.env_clear {
115            cmd.env_clear();
116        }
117
118        for (key, value) in &self.envs {
119            cmd.env(key, value);
120        }
121
122        if let Some(ref dir) = self.current_dir {
123            cmd.current_dir(dir);
124        }
125
126        use std::process::Stdio;
127
128        cmd.stdin(if self.stdin_piped {
129            Stdio::piped()
130        } else {
131            Stdio::null()
132        });
133
134        cmd.stdout(if self.stdout_piped {
135            Stdio::piped()
136        } else {
137            Stdio::inherit()
138        });
139
140        cmd.stderr(if self.stderr_piped {
141            Stdio::piped()
142        } else {
143            Stdio::inherit()
144        });
145
146        cmd
147    }
148
149    /// 启动进程(不等待)
150    pub async fn spawn(self) -> Result<Child, ProcessError> {
151        let mut cmd = self.build_command();
152        let child = cmd.spawn()?;
153        Child::new(child)
154    }
155
156    /// 执行并等待输出
157    pub async fn output(self) -> Result<ProcessOutput, ProcessError> {
158        let mut cmd = self.build_command();
159        let output = cmd.output().await?;
160        Ok(ProcessOutput::new(
161            output.status.into(),
162            output.stdout,
163            output.stderr,
164        ))
165    }
166
167    /// 执行并等待退出状态
168    pub async fn status(self) -> Result<crate::child::ExitStatus, ProcessError> {
169        let mut cmd = self.build_command();
170        let status = cmd.status().await?;
171        Ok(status.into())
172    }
173}
174
175/// 命令构建器(Builder 模式)
176#[derive(Default)]
177pub struct CommandBuilder {
178    command: Option<Command>,
179}
180
181impl CommandBuilder {
182    /// 创建新构建器
183    pub fn new(program: impl Into<String>) -> Self {
184        Self {
185            command: Some(Command::new(program)),
186        }
187    }
188
189    /// 添加参数
190    pub fn arg(mut self, arg: impl Into<String>) -> Self {
191        if let Some(cmd) = self.command.take() {
192            self.command = Some(cmd.arg(arg));
193        }
194        self
195    }
196
197    /// 添加多个参数
198    pub fn args<I, S>(mut self, args: I) -> Self
199    where
200        I: IntoIterator<Item = S>,
201        S: Into<String>,
202    {
203        if let Some(cmd) = self.command.take() {
204            self.command = Some(cmd.args(args));
205        }
206        self
207    }
208
209    /// 设置环境变量
210    pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
211        if let Some(cmd) = self.command.take() {
212            self.command = Some(cmd.env(key, value));
213        }
214        self
215    }
216
217    /// 设置工作目录
218    pub fn current_dir(mut self, dir: impl Into<PathBuf>) -> Self {
219        if let Some(cmd) = self.command.take() {
220            self.command = Some(cmd.current_dir(dir));
221        }
222        self
223    }
224
225    /// 捕获标准输入
226    pub fn stdin_piped(mut self) -> Self {
227        if let Some(cmd) = self.command.take() {
228            self.command = Some(cmd.stdin_piped());
229        }
230        self
231    }
232
233    /// 构建命令
234    pub fn build(self) -> Command {
235        self.command.unwrap_or_else(|| Command::new(""))
236    }
237
238    /// 启动进程
239    pub async fn spawn(self) -> Result<Child, ProcessError> {
240        self.build().spawn().await
241    }
242
243    /// 执行并等待输出
244    pub async fn output(self) -> Result<ProcessOutput, ProcessError> {
245        self.build().output().await
246    }
247}
248
249// ========== 便捷函数 ==========
250
251/// 执行 shell 命令
252#[cfg(windows)]
253pub async fn shell(command: &str) -> Result<ProcessOutput, ProcessError> {
254    Command::new("cmd")
255        .args(["/C", command])
256        .output()
257        .await
258}
259
260/// 执行 shell 命令
261#[cfg(unix)]
262pub async fn shell(command: &str) -> Result<ProcessOutput, ProcessError> {
263    Command::new("sh")
264        .args(["-c", command])
265        .output()
266        .await
267}
268
269/// 执行命令并返回标准输出
270pub async fn exec(program: &str, args: &[&str]) -> Result<String, ProcessError> {
271    let output = Command::new(program)
272        .args(args.iter().map(|s| s.to_string()))
273        .output()
274        .await?;
275
276    output.ensure_success()?;
277    output.stdout_string()
278}
279
280/// 检查命令是否存在
281pub async fn which(program: &str) -> Option<PathBuf> {
282    #[cfg(windows)]
283    let result = Command::new("where")
284        .arg(program)
285        .output()
286        .await
287        .ok();
288
289    #[cfg(unix)]
290    let result = Command::new("which")
291        .arg(program)
292        .output()
293        .await
294        .ok();
295
296    result.and_then(|output| {
297        if output.success() {
298            output
299                .stdout_lossy()
300                .lines()
301                .next()
302                .map(|s| PathBuf::from(s.trim()))
303        } else {
304            None
305        }
306    })
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[tokio::test]
314    async fn test_command_builder() {
315        let cmd = Command::builder("echo")
316            .arg("hello")
317            .arg("world")
318            .build();
319
320        assert_eq!(cmd.program, "echo");
321        assert_eq!(cmd.args, vec!["hello", "world"]);
322    }
323
324    #[tokio::test]
325    async fn test_env() {
326        let cmd = Command::new("env")
327            .env("MY_VAR", "my_value");
328
329        assert_eq!(cmd.envs.get("MY_VAR"), Some(&"my_value".to_string()));
330    }
331
332    #[tokio::test]
333    async fn test_shell_command() {
334        #[cfg(windows)]
335        let output = shell("echo hello").await.unwrap();
336
337        #[cfg(unix)]
338        let output = shell("echo hello").await.unwrap();
339
340        assert!(output.success());
341        assert!(output.stdout_lossy().contains("hello"));
342    }
343
344    #[tokio::test]
345    async fn test_which() {
346        // cmd/sh 应该总是存在的
347        #[cfg(windows)]
348        let result = which("cmd").await;
349
350        #[cfg(unix)]
351        let result = which("sh").await;
352
353        assert!(result.is_some());
354    }
355
356    #[tokio::test]
357    async fn test_nonexistent_command() {
358        let result = Command::new("nonexistent_command_12345")
359            .output()
360            .await;
361
362        assert!(result.is_err());
363    }
364}