1use std::path::{Path, PathBuf};
6use std::process::Command;
7
8pub fn file_exists(path: &Path) -> bool {
10 path.exists() && path.is_file()
11}
12
13pub fn dir_exists(path: &Path) -> bool {
15 path.exists() && path.is_dir()
16}
17
18pub fn get_file_extension(path: &Path) -> Option<String> {
20 path.extension()
21 .and_then(|ext| ext.to_str())
22 .map(|ext| ext.to_string())
23}
24
25pub fn get_file_size(path: &Path) -> Result<u64, Box<dyn std::error::Error>> {
27 let metadata = std::fs::metadata(path)?;
28 Ok(metadata.len())
29}
30
31pub fn find_files(dir: &Path, extension: Option<&str>) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
33 let mut files = Vec::new();
34
35 fn visit_dir(dir: &Path, extension: Option<&str>, files: &mut Vec<PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
36 if dir.is_dir() {
37 for entry in std::fs::read_dir(dir)? {
38 let entry = entry?;
39 let path = entry.path();
40
41 if path.is_dir() {
42 visit_dir(&path, extension, files)?;
43 } else if let Some(ext) = extension {
44 if let Some(file_ext) = get_file_extension(&path) {
45 if file_ext == ext {
46 files.push(path);
47 }
48 }
49 } else {
50 files.push(path);
51 }
52 }
53 }
54 Ok(())
55 }
56
57 visit_dir(dir, extension, &mut files)?;
58 Ok(files)
59}
60
61pub fn execute_command(command: &str, args: &[&str], cwd: Option<&Path>) -> Result<String, Box<dyn std::error::Error>> {
63 let mut cmd = Command::new(command);
64 cmd.args(args);
65
66 if let Some(dir) = cwd {
67 cmd.current_dir(dir);
68 }
69
70 let output = cmd.output()?;
71
72 if output.status.success() {
73 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
74 Ok(stdout)
75 } else {
76 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
77 Err(format!("Command failed: {}", stderr).into())
78 }
79}
80
81pub fn is_process_running(pid: u32) -> bool {
83 #[cfg(unix)]
85 {
86 use std::process::Command;
87 Command::new("kill")
88 .args(&["-0", &pid.to_string()])
89 .output()
90 .map(|output| output.status.success())
91 .unwrap_or(false)
92 }
93
94 #[cfg(windows)]
96 {
97 use std::process::Command;
98 Command::new("tasklist")
99 .args(&["/FI", &format!("PID eq {}", pid)])
100 .output()
101 .map(|output| {
102 let stdout = String::from_utf8_lossy(&output.stdout);
103 stdout.contains(&pid.to_string())
104 })
105 .unwrap_or(false)
106 }
107
108 #[cfg(not(any(unix, windows)))]
110 {
111 false
112 }
113}
114
115pub fn find_available_port(start_port: u16) -> Option<u16> {
117 use std::net::TcpListener;
118
119 for port in start_port..65535 {
120 if TcpListener::bind(("127.0.0.1", port)).is_ok() {
121 return Some(port);
122 }
123 }
124 None
125}
126
127pub fn compare_versions(version1: &str, version2: &str) -> std::cmp::Ordering {
129 let v1_parts: Vec<&str> = version1.split('.').collect();
130 let v2_parts: Vec<&str> = version2.split('.').collect();
131
132 for (v1, v2) in v1_parts.iter().zip(v2_parts.iter()) {
133 let v1_num = v1.parse::<u32>().unwrap_or(0);
134 let v2_num = v2.parse::<u32>().unwrap_or(0);
135
136 match v1_num.cmp(&v2_num) {
137 std::cmp::Ordering::Equal => continue,
138 other => return other,
139 }
140 }
141
142 v1_parts.len().cmp(&v2_parts.len())
143}
144
145pub fn format_bytes(bytes: u64) -> String {
147 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
148
149 if bytes == 0 {
150 return "0 B".to_string();
151 }
152
153 let base = 1024_f64;
154 let log = (bytes as f64).log(base).floor() as usize;
155 let unit_index = log.min(UNITS.len() - 1);
156 let value = bytes as f64 / base.powi(unit_index as i32);
157
158 format!("{:.1} {}", value, UNITS[unit_index])
159}
160
161pub fn format_duration(duration: std::time::Duration) -> String {
163 let total_seconds = duration.as_secs();
164
165 if total_seconds < 60 {
166 format!("{}s", total_seconds)
167 } else if total_seconds < 3600 {
168 let minutes = total_seconds / 60;
169 let seconds = total_seconds % 60;
170 format!("{}m {}s", minutes, seconds)
171 } else {
172 let hours = total_seconds / 3600;
173 let minutes = (total_seconds % 3600) / 60;
174 format!("{}h {}m", hours, minutes)
175 }
176}
177
178pub fn to_camel_case(s: &str) -> String {
180 let mut result = String::new();
181 let mut capitalize_next = false;
182
183 for (i, ch) in s.chars().enumerate() {
184 if ch == '_' || ch == '-' {
185 capitalize_next = true;
186 } else if capitalize_next || i == 0 {
187 result.extend(ch.to_uppercase());
188 capitalize_next = false;
189 } else {
190 result.extend(ch.to_lowercase());
191 }
192 }
193
194 result
195}
196
197pub fn to_snake_case(s: &str) -> String {
199 let mut result = String::new();
200
201 for (i, ch) in s.chars().enumerate() {
202 if ch.is_uppercase() && i > 0 {
203 result.push('_');
204 }
205 result.extend(ch.to_lowercase());
206 }
207
208 result
209}
210
211pub fn get_env_var(key: &str, default: &str) -> String {
213 std::env::var(key).unwrap_or_else(|_| default.to_string())
214}
215
216pub fn create_temp_file(prefix: &str, suffix: &str) -> Result<std::fs::File, Box<dyn std::error::Error>> {
218 use std::fs::File;
219 use std::io::Write;
220
221 let temp_path = std::env::temp_dir().join(format!("{}{}", prefix, suffix));
222 let file = File::create(&temp_path)?;
223 Ok(file)
224}
225
226pub fn get_line_ending() -> &'static str {
228 if cfg!(windows) {
229 "\r\n"
230 } else {
231 "\n"
232 }
233}
234
235pub fn get_platform_name() -> &'static str {
237 if cfg!(windows) {
238 "windows"
239 } else if cfg!(macos) {
240 "macos"
241 } else if cfg!(linux) {
242 "linux"
243 } else {
244 "unknown"
245 }
246}
247
248pub struct ProgressBar {
251 total: usize,
252 current: usize,
253 width: usize,
254 title: String,
255}
256
257impl ProgressBar {
258 pub fn new(total: usize, title: impl Into<String>) -> Self {
260 Self {
261 total,
262 current: 0,
263 width: 50,
264 title: title.into(),
265 }
266 }
267
268 pub fn update(&mut self, current: usize) {
270 self.current = current.min(self.total);
271 self.display();
272 }
273
274 pub fn inc(&mut self) {
276 self.update(self.current + 1);
277 }
278
279 pub fn finish(&mut self) {
281 self.update(self.total);
282 println!(); }
284
285 fn display(&self) {
287 let percentage = if self.total > 0 {
288 (self.current as f64 / self.total as f64 * 100.0) as usize
289 } else {
290 100
291 };
292
293 let filled = (self.current as f64 / self.total as f64 * self.width as f64) as usize;
294 let filled = filled.min(self.width);
295
296 let bar = "█".repeat(filled) + &"░".repeat(self.width - filled);
297
298 print!("\r{} [{:<width$}] {}/{} ({}%)",
299 self.title,
300 bar,
301 self.current,
302 self.total,
303 percentage,
304 width = self.width
305 );
306 std::io::Write::flush(&mut std::io::stdout()).ok();
307 }
308}
309
310impl Drop for ProgressBar {
311 fn drop(&mut self) {
312 println!(); }
314}