use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
static QUIET: AtomicBool = AtomicBool::new(false);
pub const ARROW: &str = "→";
pub const CHECKMARK: &str = "✓";
pub fn set_quiet(quiet: bool) {
QUIET.store(quiet, Ordering::SeqCst);
}
pub fn is_quiet() -> bool {
QUIET.load(Ordering::SeqCst)
}
pub fn success(msg: &str) {
if !is_quiet() {
println!("{} {}", "✓".green(), msg);
}
}
pub fn info(msg: &str) {
if !is_quiet() {
println!("{}", msg);
}
}
pub fn warn(msg: &str) {
eprintln!("{} {}", "⚠".yellow(), msg);
}
pub fn error(msg: &str) {
eprintln!("{} {}", "✗".red(), msg);
}
pub fn hint(msg: &str) {
if !is_quiet() {
println!("{} {}", "→".cyan(), msg.dimmed());
}
}
pub fn branch(name: &str, is_current: bool) -> String {
if is_current {
format!("{}", name.green().bold())
} else {
name.to_string()
}
}
pub fn bold(text: &str) -> String {
format!("{}", text.bold())
}
pub fn mr_number(num: u64) -> String {
format!("{}", format!("#{}", num).cyan())
}
#[allow(dead_code)]
#[deprecated(note = "Use mr_number() instead")]
pub fn pr_number(num: u64) -> String {
mr_number(num)
}
#[allow(dead_code)]
pub fn format_stack_tree(
entries: &[(String, bool, usize, Option<u64>)], ) -> String {
let mut output = String::new();
for (i, (name, is_current, depth, pr_num)) in entries.iter().enumerate() {
let is_last = i == entries.len() - 1
|| entries
.get(i + 1)
.map(|(_, _, d, _)| *d <= *depth)
.unwrap_or(true);
let mut prefix = String::new();
for _ in 0..*depth {
prefix.push_str("│ ");
}
let connector = if is_last { "└─" } else { "├─" };
let marker = if *is_current { "◉" } else { "○" };
let name_str = if *is_current {
name.green().bold().to_string()
} else {
name.to_string()
};
let pr_str = pr_num
.map(|n| format!(" {}", format!("#{}", n).cyan()))
.unwrap_or_default();
output.push_str(&format!(
"{}{} {} {}{}\n",
prefix, connector, marker, name_str, pr_str
));
}
output
}
pub fn confirm(msg: &str) -> bool {
use dialoguer::Confirm;
Confirm::new()
.with_prompt(msg)
.default(false)
.interact()
.unwrap_or(false)
}
pub fn select(msg: &str, options: &[&str]) -> Option<usize> {
use dialoguer::Select;
Select::new()
.with_prompt(msg)
.items(options)
.interact_opt()
.ok()
.flatten()
}
pub fn input(msg: &str) -> Option<String> {
use dialoguer::Input;
Input::new()
.with_prompt(msg)
.interact_text()
.ok()
}
pub fn spinner(msg: &str) -> ProgressBar {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
.template("{spinner:.cyan} {msg}")
.expect("Invalid template"),
);
pb.set_message(msg.to_string());
pb.enable_steady_tick(Duration::from_millis(80));
pb
}
pub fn progress_bar(len: u64, msg: &str) -> ProgressBar {
let pb = ProgressBar::new(len);
pb.set_style(
ProgressStyle::default_bar()
.template("{msg} [{bar:30.cyan/dim}] {pos}/{len}")
.expect("Invalid template")
.progress_chars("━━╸"),
);
pb.set_message(msg.to_string());
pb
}
pub fn finish_progress(pb: &ProgressBar, msg: &str) {
pb.set_style(
ProgressStyle::default_spinner()
.template("{msg}")
.expect("Invalid template"),
);
pb.finish_with_message(format!("{} {}", "✓".green(), msg));
}
pub fn finish_progress_error(pb: &ProgressBar, msg: &str) {
pb.set_style(
ProgressStyle::default_spinner()
.template("{msg}")
.expect("Invalid template"),
);
pb.finish_with_message(format!("{} {}", "✗".red(), msg));
}