doum_cli/tools/
executor.rs1use crate::system::env::{OsType, ShellType, SystemInfo};
2use crate::system::error::{DoumError, DoumResult};
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) -> DoumResult<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) -> DoumResult<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(
44 command: &str,
45 shell: &ShellType,
46 _timeout: Option<Duration>,
47) -> DoumResult<Output> {
48 let mut cmd = match shell {
49 ShellType::PowerShell => {
50 let mut c = Command::new("powershell.exe");
51 c.arg("-NoProfile");
52 c.arg("-Command");
53 c.arg(format!(
55 "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; {}",
56 command
57 ));
58 c
59 }
60 ShellType::Cmd
61 | ShellType::Bash
62 | ShellType::Zsh
63 | ShellType::Fish
64 | ShellType::Unknown => {
65 let mut c = Command::new("cmd.exe");
67 c.arg("/C");
68 c.arg(format!("chcp 65001 >nul && {}", command));
70 c
71 }
72 };
73
74 let output = cmd
76 .output()
77 .map_err(|e| DoumError::CommandExecution(format!("명령 실행 실패: {}", e)))?;
78
79 Ok(output)
80}
81
82fn execute_unix(
84 command: &str,
85 shell: &ShellType,
86 _timeout: Option<Duration>,
87) -> DoumResult<Output> {
88 let shell_path = match shell {
89 ShellType::Bash => "/bin/bash",
90 ShellType::Zsh => "/bin/zsh",
91 ShellType::Fish => "/usr/bin/fish",
92 _ => "/bin/sh", };
94
95 let mut cmd = Command::new(shell_path);
96 cmd.arg("-c");
97 cmd.arg(command);
98
99 let output = cmd
100 .output()
101 .map_err(|e| DoumError::CommandExecution(format!("명령 실행 실패: {}", e)))?;
102
103 Ok(output)
104}
105
106impl CommandOutput {
107 pub fn stdout_string(&self) -> String {
109 String::from_utf8_lossy(&self.stdout).to_string()
110 }
111
112 pub fn stderr_string(&self) -> String {
114 String::from_utf8_lossy(&self.stderr).to_string()
115 }
116
117 pub fn display(&self) -> String {
119 let mut result = String::new();
120
121 if !self.stdout.is_empty() {
122 result.push_str(&self.stdout_string());
123 }
124
125 if !self.stderr.is_empty() {
126 if !result.is_empty() {
127 result.push('\n');
128 }
129 result.push_str("=== stderr ===\n");
130 result.push_str(&self.stderr_string());
131 }
132
133 if !self.success {
134 if !result.is_empty() {
135 result.push('\n');
136 }
137 result.push_str(&format!("Exit code: {}", self.exit_code));
138 }
139
140 result
141 }
142}