doum_cli/tools/
executor.rs1use crate::system::error::{DoumError, Result};
2use crate::system::env::{SystemInfo, OsType, ShellType};
3use std::process::{Command, Output};
4use std::time::Duration;
5
6#[derive(Debug)]
8pub struct CommandOutput {
9 pub success: bool,
10 pub exit_code: i32,
11 pub stdout: Vec<u8>,
12 pub stderr: Vec<u8>,
13}
14
15pub fn execute(command: &str, system_info: &SystemInfo) -> Result<CommandOutput> {
17 execute_with_timeout(command, system_info, None)
18}
19
20pub fn execute_with_timeout(
22 command: &str,
23 system_info: &SystemInfo,
24 timeout: Option<Duration>,
25) -> Result<CommandOutput> {
26 let output = match system_info.os {
27 OsType::Windows => execute_windows(command, &system_info.shell, timeout)?,
28 OsType::Linux | OsType::MacOS => execute_unix(command, &system_info.shell, timeout)?,
29 };
30
31 let exit_code = output.status.code().unwrap_or(-1);
32 let success = output.status.success();
33
34 Ok(CommandOutput {
35 stdout: output.stdout,
36 stderr: output.stderr,
37 exit_code,
38 success,
39 })
40}
41
42fn execute_windows(command: &str, shell: &ShellType, _timeout: Option<Duration>) -> Result<Output> {
44 let mut cmd = match shell {
45 ShellType::PowerShell => {
46 let mut c = Command::new("powershell.exe");
47 c.arg("-NoProfile");
48 c.arg("-Command");
49 c.arg(format!("[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; {}", command));
51 c
52 }
53 ShellType::Cmd | ShellType::Bash | ShellType::Zsh | ShellType::Fish | ShellType::Unknown => {
54 let mut c = Command::new("cmd.exe");
56 c.arg("/C");
57 c.arg(format!("chcp 65001 >nul && {}", command));
59 c
60 }
61 };
62
63 let output = cmd
65 .output()
66 .map_err(|e| DoumError::CommandExecution(format!("명령 실행 실패: {}", e)))?;
67
68 Ok(output)
69}
70
71fn execute_unix(command: &str, shell: &ShellType, _timeout: Option<Duration>) -> Result<Output> {
73 let shell_path = match shell {
74 ShellType::Bash => "/bin/bash",
75 ShellType::Zsh => "/bin/zsh",
76 ShellType::Fish => "/usr/bin/fish",
77 _ => "/bin/sh", };
79
80 let mut cmd = Command::new(shell_path);
81 cmd.arg("-c");
82 cmd.arg(command);
83
84 let output = cmd
85 .output()
86 .map_err(|e| DoumError::CommandExecution(format!("명령 실행 실패: {}", e)))?;
87
88 Ok(output)
89}
90
91impl CommandOutput {
92 pub fn stdout_string(&self) -> String {
94 String::from_utf8_lossy(&self.stdout).to_string()
95 }
96
97 pub fn stderr_string(&self) -> String {
99 String::from_utf8_lossy(&self.stderr).to_string()
100 }
101
102 pub fn display(&self) -> String {
104 let mut result = String::new();
105
106 if !self.stdout.is_empty() {
107 result.push_str(&self.stdout_string());
108 }
109
110 if !self.stderr.is_empty() {
111 if !result.is_empty() {
112 result.push('\n');
113 }
114 result.push_str("=== stderr ===\n");
115 result.push_str(&self.stderr_string());
116 }
117
118 if !self.success {
119 if !result.is_empty() {
120 result.push('\n');
121 }
122 result.push_str(&format!("Exit code: {}", self.exit_code));
123 }
124
125 result
126 }
127}