Skip to main content

dev_prune/
output.rs

1// Copyright 2026 VKrishna04
2// SPDX-License-Identifier: Apache-2.0
3
4// Pretty-print helpers for terminal output.
5//
6// Provides colored, formatted output for CLI commands and terminal spinners.
7
8use colored::Colorize;
9use indicatif::{ProgressBar, ProgressStyle};
10use std::path::Path;
11use std::time::Duration;
12
13/// Helper to strip Windows UNC `\\?\` prefix, macOS `/private/` prefix, and collapse double slashes.
14pub 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"\\?\") {
17        stripped.to_string()
18    } else {
19        s
20    };
21    let s = if let Some(stripped) = s.strip_prefix("/private/var/") {
22        format!("/var/{stripped}")
23    } else if let Some(stripped) = s.strip_prefix("/private/tmp/") {
24        format!("/tmp/{stripped}")
25    } else {
26        s
27    };
28    s.replace("//", "/")
29}
30
31/// Create an animated terminal loading spinner for long-running operations.
32pub fn create_spinner(msg: &'static str) -> ProgressBar {
33    let pb = ProgressBar::new_spinner();
34    pb.set_style(
35        ProgressStyle::default_spinner()
36            .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
37            .template("{spinner:.cyan} {msg}")
38            .expect("Invalid progress bar template"),
39    );
40    pb.set_message(msg);
41    pb.enable_steady_tick(Duration::from_millis(80));
42    pb
43}
44
45/// Print a success message (green checkmark)
46pub fn print_success(msg: &str) {
47    println!("{} {}", "✓".green().bold(), msg);
48}
49
50/// Print a warning message (yellow exclamation)
51pub fn print_warning(msg: &str) {
52    println!("{} {}", "⚠".yellow().bold(), msg);
53}
54
55/// Print an error message (red X)
56pub fn print_error(msg: &str) {
57    eprintln!("{} {}", "✗".red().bold(), msg);
58}
59
60/// Print an info message (blue arrow)
61pub fn print_info(msg: &str) {
62    println!("{} {}", "→".blue().bold(), msg);
63}
64
65/// Print a notice to stderr.
66///
67/// For anything the user should see that is *about* the command rather than part of its
68/// output — a deprecated flag, say. It has to be stderr: `--json` promises stdout carries
69/// one JSON document and nothing else, and a friendly note printed above it is the
70/// difference between a parseable contract and a parse error.
71pub fn print_notice(msg: &str) {
72    eprintln!("{} {}", "→".blue().bold(), msg);
73}
74
75/// Print a section header
76pub fn print_header(msg: &str) {
77    println!("\n{}", msg.bold().underline());
78}
79
80/// Print the dev-prune ASCII art banner
81pub fn print_banner() {
82    let art = format!(
83        r#"
84 ___    _____ __     __    ____  ____  _   _ _   _ _____
85|  _ \ | ____|\ \   / /   |  _ \|  _ \| | | | \ | | ____|
86| | | ||  _|   \ \ / /    | |_) | |_) | | | |  \| |  _|
87| |_| || |___   \ V /     |  __/|  _ <| |_| | |\  | |___
88|____/ |_____|   \_/      |_|   |_| \_\\___/|_| \_|_____| v{}
89"#,
90        crate::constants::VERSION
91    );
92    println!("{}", art.truecolor(64, 224, 208).bold());
93}
94
95/// Pick the singular or plural form for a count.
96///
97/// Small, but "Unregistered 1 repositories" is the kind of thing people notice and
98/// nothing else in the codebase was doing it consistently.
99pub fn plural<'a>(count: usize, one: &'a str, many: &'a str) -> &'a str {
100    if count == 1 { one } else { many }
101}
102
103/// Format bytes into human-readable string (e.g., "1.2 GB", "450 MB")
104pub fn format_bytes(bytes: u64) -> String {
105    use humansize::{BINARY, format_size};
106    format_size(bytes, BINARY)
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_format_bytes() {
115        assert_eq!(format_bytes(0), "0 B");
116        assert_eq!(format_bytes(1024), "1 KiB");
117        assert_eq!(format_bytes(1024 * 1024), "1 MiB");
118        assert_eq!(format_bytes(1024 * 1024 * 1024), "1 GiB");
119    }
120
121    #[test]
122    fn test_clean_path() {
123        assert_eq!(clean_path(r"\\?\C:\Users\krish"), r"C:\Users\krish");
124        assert_eq!(clean_path(r"/private/var/tmp/repo"), r"/var/tmp/repo");
125        assert_eq!(clean_path(r"//home//user//repo"), r"/home/user/repo");
126    }
127}