use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use std::path::Path;
use std::time::Duration;
pub fn clean_path<P: AsRef<Path>>(path: P) -> String {
let s = path.as_ref().display().to_string();
let s = if let Some(stripped) = s.strip_prefix(r"\\?\UNC\") {
format!(r"\\{stripped}")
} else if let Some(stripped) = s.strip_prefix(r"\\?\") {
stripped.to_string()
} else {
s
};
let s = if let Some(stripped) = s.strip_prefix("/private/var/") {
format!("/var/{stripped}")
} else if let Some(stripped) = s.strip_prefix("/private/tmp/") {
format!("/tmp/{stripped}")
} else {
s
};
s.replace("//", "/")
}
pub fn create_spinner(msg: &'static str) -> ProgressBar {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
.template("{spinner:.cyan} {msg}")
.expect("Invalid progress bar template"),
);
pb.set_message(msg);
pb.enable_steady_tick(Duration::from_millis(80));
pb
}
fn highlight_code_spans(msg: &str) -> String {
if !msg.contains('`') {
return msg.to_string();
}
let mut out = String::with_capacity(msg.len() + 16);
let mut rest = msg;
while let Some(start) = rest.find('`') {
let Some(len) = rest[start + 1..].find('`') else {
break;
};
out.push_str(&rest[..start]);
out.push('`');
out.push_str(&rest[start + 1..start + 1 + len].cyan().to_string());
out.push('`');
rest = &rest[start + len + 2..];
}
out.push_str(rest);
out
}
pub fn print_success(msg: &str) {
println!("{} {}", "✓".green().bold(), highlight_code_spans(msg));
}
pub fn print_warning(msg: &str) {
eprintln!("{} {}", "⚠".yellow().bold(), highlight_code_spans(msg));
}
pub fn print_error(msg: &str) {
eprintln!("{} {}", "✗".red().bold(), highlight_code_spans(msg));
}
pub fn print_info(msg: &str) {
println!("{} {}", "→".blue().bold(), highlight_code_spans(msg));
}
pub fn print_notice(msg: &str) {
eprintln!("{} {}", "→".blue().bold(), highlight_code_spans(msg));
}
pub fn print_header(msg: &str) {
println!("\n{}", msg.cyan().bold().underline());
}
pub fn format_bytes_styled(bytes: u64) -> String {
format_bytes(bytes).green().bold().to_string()
}
pub fn styled_path<P: AsRef<Path>>(path: P) -> String {
clean_path(path).cyan().to_string()
}
pub fn print_banner() {
let art = format!(
r#"
___ _____ __ __ ____ ____ _ _ _ _ _____
| _ \ | ____|\ \ / / | _ \| _ \| | | | \ | | ____|
| | | || _| \ \ / / | |_) | |_) | | | | \| | _|
| |_| || |___ \ V / | __/| _ <| |_| | |\ | |___
|____/ |_____| \_/ |_| |_| \_\\___/|_| \_|_____| v{}
"#,
crate::constants::VERSION
);
println!("{}", art.truecolor(64, 224, 208).bold());
}
pub fn print_attribution() {
use std::io::IsTerminal;
if std::io::stdout().is_terminal() {
println!("{}", crate::constants::ATTRIBUTION_LINE.dimmed());
}
}
pub fn plural<'a>(count: usize, one: &'a str, many: &'a str) -> &'a str {
if count == 1 { one } else { many }
}
pub fn format_bytes(bytes: u64) -> String {
use humansize::{BINARY, format_size};
format_size(bytes, BINARY)
}
pub fn shared_note(shared_bytes: u64, adapter: &str) -> String {
if shared_bytes == 0 {
String::new()
} else {
format!(
" (+{} hardlinked into the {adapter} store — not counted, the store keeps them)",
format_bytes(shared_bytes)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_bytes() {
assert_eq!(format_bytes(0), "0 B");
assert_eq!(format_bytes(1024), "1 KiB");
assert_eq!(format_bytes(1024 * 1024), "1 MiB");
assert_eq!(format_bytes(1024 * 1024 * 1024), "1 GiB");
}
#[test]
fn code_spans_survive_highlighting_verbatim_when_color_is_off() {
colored::control::set_override(false);
assert_eq!(
highlight_code_spans("run `devp setup` again"),
"run `devp setup` again"
);
assert_eq!(highlight_code_spans("no spans here"), "no spans here");
assert_eq!(
highlight_code_spans("odd `tick remains"),
"odd `tick remains"
);
assert_eq!(
highlight_code_spans("`a` and `b`, plus `stray"),
"`a` and `b`, plus `stray"
);
colored::control::unset_override();
}
#[test]
fn test_clean_path() {
assert_eq!(clean_path(r"\\?\C:\Users\krish"), r"C:\Users\krish");
assert_eq!(
clean_path(r"\\?\UNC\server\share\repo"),
r"\\server\share\repo"
);
assert_eq!(clean_path(r"/private/var/tmp/repo"), r"/var/tmp/repo");
assert_eq!(clean_path(r"//home//user//repo"), r"/home/user/repo");
}
}