1use colored::Colorize;
9use indicatif::{ProgressBar, ProgressStyle};
10use std::path::Path;
11use std::time::Duration;
12
13pub fn clean_path<P: AsRef<Path>>(path: P) -> String {
15 let s = path.as_ref().display().to_string();
16 let s = if let Some(stripped) = s.strip_prefix(r"\\?\UNC\") {
20 format!(r"\\{stripped}")
21 } else if let Some(stripped) = s.strip_prefix(r"\\?\") {
22 stripped.to_string()
23 } else {
24 s
25 };
26 let s = if let Some(stripped) = s.strip_prefix("/private/var/") {
27 format!("/var/{stripped}")
28 } else if let Some(stripped) = s.strip_prefix("/private/tmp/") {
29 format!("/tmp/{stripped}")
30 } else {
31 s
32 };
33 s.replace("//", "/")
34}
35
36pub fn create_spinner(msg: &'static str) -> ProgressBar {
38 let pb = ProgressBar::new_spinner();
39 pb.set_style(
40 ProgressStyle::default_spinner()
41 .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
42 .template("{spinner:.cyan} {msg}")
43 .expect("Invalid progress bar template"),
44 );
45 pb.set_message(msg);
46 pb.enable_steady_tick(Duration::from_millis(80));
47 pb
48}
49
50pub fn print_success(msg: &str) {
52 println!("{} {}", "✓".green().bold(), msg);
53}
54
55pub fn print_warning(msg: &str) {
61 eprintln!("{} {}", "⚠".yellow().bold(), msg);
62}
63
64pub fn print_error(msg: &str) {
66 eprintln!("{} {}", "✗".red().bold(), msg);
67}
68
69pub fn print_info(msg: &str) {
71 println!("{} {}", "→".blue().bold(), msg);
72}
73
74pub fn print_notice(msg: &str) {
81 eprintln!("{} {}", "→".blue().bold(), msg);
82}
83
84pub fn print_header(msg: &str) {
86 println!("\n{}", msg.bold().underline());
87}
88
89pub fn print_banner() {
91 let art = format!(
92 r#"
93 ___ _____ __ __ ____ ____ _ _ _ _ _____
94| _ \ | ____|\ \ / / | _ \| _ \| | | | \ | | ____|
95| | | || _| \ \ / / | |_) | |_) | | | | \| | _|
96| |_| || |___ \ V / | __/| _ <| |_| | |\ | |___
97|____/ |_____| \_/ |_| |_| \_\\___/|_| \_|_____| v{}
98"#,
99 crate::constants::VERSION
100 );
101 println!("{}", art.truecolor(64, 224, 208).bold());
102}
103
104pub fn print_attribution() {
113 use std::io::IsTerminal;
114 if std::io::stdout().is_terminal() {
115 println!("{}", crate::constants::ATTRIBUTION_LINE.dimmed());
116 }
117}
118
119pub fn plural<'a>(count: usize, one: &'a str, many: &'a str) -> &'a str {
124 if count == 1 { one } else { many }
125}
126
127pub fn format_bytes(bytes: u64) -> String {
129 use humansize::{BINARY, format_size};
130 format_size(bytes, BINARY)
131}
132
133pub fn shared_note(shared_bytes: u64, adapter: &str) -> String {
141 if shared_bytes == 0 {
142 String::new()
143 } else {
144 format!(
145 " (+{} hardlinked into the {adapter} store — not counted, the store keeps them)",
146 format_bytes(shared_bytes)
147 )
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_format_bytes() {
157 assert_eq!(format_bytes(0), "0 B");
158 assert_eq!(format_bytes(1024), "1 KiB");
159 assert_eq!(format_bytes(1024 * 1024), "1 MiB");
160 assert_eq!(format_bytes(1024 * 1024 * 1024), "1 GiB");
161 }
162
163 #[test]
164 fn test_clean_path() {
165 assert_eq!(clean_path(r"\\?\C:\Users\krish"), r"C:\Users\krish");
166 assert_eq!(
167 clean_path(r"\\?\UNC\server\share\repo"),
168 r"\\server\share\repo"
169 );
170 assert_eq!(clean_path(r"/private/var/tmp/repo"), r"/var/tmp/repo");
171 assert_eq!(clean_path(r"//home//user//repo"), r"/home/user/repo");
172 }
173}