use std::path::{Path, PathBuf};
use std::process::Command;
pub fn file_exists(path: &Path) -> bool {
path.exists() && path.is_file()
}
pub fn dir_exists(path: &Path) -> bool {
path.exists() && path.is_dir()
}
pub fn get_file_extension(path: &Path) -> Option<String> {
path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.to_string())
}
pub fn get_file_size(path: &Path) -> Result<u64, Box<dyn std::error::Error>> {
let metadata = std::fs::metadata(path)?;
Ok(metadata.len())
}
pub fn find_files(dir: &Path, extension: Option<&str>) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
let mut files = Vec::new();
fn visit_dir(dir: &Path, extension: Option<&str>, files: &mut Vec<PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
if dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
visit_dir(&path, extension, files)?;
} else if let Some(ext) = extension {
if let Some(file_ext) = get_file_extension(&path) {
if file_ext == ext {
files.push(path);
}
}
} else {
files.push(path);
}
}
}
Ok(())
}
visit_dir(dir, extension, &mut files)?;
Ok(files)
}
pub fn execute_command(command: &str, args: &[&str], cwd: Option<&Path>) -> Result<String, Box<dyn std::error::Error>> {
let mut cmd = Command::new(command);
cmd.args(args);
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
let output = cmd.output()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
Ok(stdout)
} else {
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
Err(format!("Command failed: {}", stderr).into())
}
}
pub fn is_process_running(pid: u32) -> bool {
#[cfg(unix)]
{
use std::process::Command;
Command::new("kill")
.args(&["-0", &pid.to_string()])
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
#[cfg(windows)]
{
use std::process::Command;
Command::new("tasklist")
.args(&["/FI", &format!("PID eq {}", pid)])
.output()
.map(|output| {
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.contains(&pid.to_string())
})
.unwrap_or(false)
}
#[cfg(not(any(unix, windows)))]
{
false
}
}
pub fn find_available_port(start_port: u16) -> Option<u16> {
use std::net::TcpListener;
for port in start_port..65535 {
if TcpListener::bind(("127.0.0.1", port)).is_ok() {
return Some(port);
}
}
None
}
pub fn compare_versions(version1: &str, version2: &str) -> std::cmp::Ordering {
let v1_parts: Vec<&str> = version1.split('.').collect();
let v2_parts: Vec<&str> = version2.split('.').collect();
for (v1, v2) in v1_parts.iter().zip(v2_parts.iter()) {
let v1_num = v1.parse::<u32>().unwrap_or(0);
let v2_num = v2.parse::<u32>().unwrap_or(0);
match v1_num.cmp(&v2_num) {
std::cmp::Ordering::Equal => continue,
other => return other,
}
}
v1_parts.len().cmp(&v2_parts.len())
}
pub fn format_bytes(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
if bytes == 0 {
return "0 B".to_string();
}
let base = 1024_f64;
let log = (bytes as f64).log(base).floor() as usize;
let unit_index = log.min(UNITS.len() - 1);
let value = bytes as f64 / base.powi(unit_index as i32);
format!("{:.1} {}", value, UNITS[unit_index])
}
pub fn format_duration(duration: std::time::Duration) -> String {
let total_seconds = duration.as_secs();
if total_seconds < 60 {
format!("{}s", total_seconds)
} else if total_seconds < 3600 {
let minutes = total_seconds / 60;
let seconds = total_seconds % 60;
format!("{}m {}s", minutes, seconds)
} else {
let hours = total_seconds / 3600;
let minutes = (total_seconds % 3600) / 60;
format!("{}h {}m", hours, minutes)
}
}
pub fn to_camel_case(s: &str) -> String {
let mut result = String::new();
let mut capitalize_next = false;
for (i, ch) in s.chars().enumerate() {
if ch == '_' || ch == '-' {
capitalize_next = true;
} else if capitalize_next || i == 0 {
result.extend(ch.to_uppercase());
capitalize_next = false;
} else {
result.extend(ch.to_lowercase());
}
}
result
}
pub fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, ch) in s.chars().enumerate() {
if ch.is_uppercase() && i > 0 {
result.push('_');
}
result.extend(ch.to_lowercase());
}
result
}
pub fn get_env_var(key: &str, default: &str) -> String {
std::env::var(key).unwrap_or_else(|_| default.to_string())
}
pub fn create_temp_file(prefix: &str, suffix: &str) -> Result<std::fs::File, Box<dyn std::error::Error>> {
use std::fs::File;
use std::io::Write;
let temp_path = std::env::temp_dir().join(format!("{}{}", prefix, suffix));
let file = File::create(&temp_path)?;
Ok(file)
}
pub fn get_line_ending() -> &'static str {
if cfg!(windows) {
"\r\n"
} else {
"\n"
}
}
pub fn get_platform_name() -> &'static str {
if cfg!(windows) {
"windows"
} else if cfg!(macos) {
"macos"
} else if cfg!(linux) {
"linux"
} else {
"unknown"
}
}
pub struct ProgressBar {
total: usize,
current: usize,
width: usize,
title: String,
}
impl ProgressBar {
pub fn new(total: usize, title: impl Into<String>) -> Self {
Self {
total,
current: 0,
width: 50,
title: title.into(),
}
}
pub fn update(&mut self, current: usize) {
self.current = current.min(self.total);
self.display();
}
pub fn inc(&mut self) {
self.update(self.current + 1);
}
pub fn finish(&mut self) {
self.update(self.total);
println!(); }
fn display(&self) {
let percentage = if self.total > 0 {
(self.current as f64 / self.total as f64 * 100.0) as usize
} else {
100
};
let filled = (self.current as f64 / self.total as f64 * self.width as f64) as usize;
let filled = filled.min(self.width);
let bar = "█".repeat(filled) + &"░".repeat(self.width - filled);
print!("\r{} [{:<width$}] {}/{} ({}%)",
self.title,
bar,
self.current,
self.total,
percentage,
width = self.width
);
std::io::Write::flush(&mut std::io::stdout()).ok();
}
}
impl Drop for ProgressBar {
fn drop(&mut self) {
println!(); }
}