use std::time::Duration;
use crate::error::Error;
use clap::builder::styling;
use crossterm::style::Stylize;
use inquire::ui::{Color, ErrorMessageRenderConfig, RenderConfig, StyleSheet, Styled};
pub const ACCENT: (u8, u8, u8) = (0xe6, 0x10, 0x48);
fn accent() -> crossterm::style::Color {
let (r, g, b) = ACCENT;
crossterm::style::Color::Rgb { r, g, b }
}
pub fn help_styles() -> styling::Styles {
let (r, g, b) = ACCENT;
let accent = styling::Style::new().fg_color(Some(styling::RgbColor(r, g, b).into()));
let dimmed = styling::Style::new().fg_color(Some(styling::AnsiColor::BrightBlack.into()));
styling::Styles::styled()
.header(styling::Style::new())
.usage(styling::Style::new())
.literal(accent)
.placeholder(dimmed)
.error(accent)
.valid(accent)
.invalid(dimmed)
}
pub fn print_error(message: &str) {
eprintln!("{}", format!("✗ {message}").with(accent()));
}
pub fn success_line(message: &str) -> String {
format!("{} {message}", "✓".with(accent()))
}
pub fn print_success(message: &str) {
println!("{}", success_line(message));
}
pub fn bar_println(bar: &indicatif::ProgressBar, line: &str) {
bar.suspend(|| println!("{line}"));
}
pub fn progress_bar(len: u64) -> indicatif::ProgressBar {
let template = format!("{{msg}} {} {{pos}}/{{len}}", "{bar:40}".with(accent()));
let style = indicatif::ProgressStyle::with_template(&template)
.expect("progress bar template is valid")
.progress_chars("━╸─");
let bar = indicatif::ProgressBar::new(len);
bar.set_style(style);
bar
}
pub fn spinner(message: &str) -> indicatif::ProgressBar {
let template = format!("{} {{msg}}", "{spinner}".with(accent()));
let style =
indicatif::ProgressStyle::with_template(&template).expect("spinner template is valid");
let spinner = indicatif::ProgressBar::new_spinner();
spinner.set_style(style);
spinner.set_message(message.to_string());
spinner.enable_steady_tick(Duration::from_millis(80));
spinner
}
pub fn with_spinner<T>(message: &str, work: impl FnOnce() -> Result<T, Error>) -> Result<T, Error> {
let spinner = spinner(message);
let result = work();
spinner.finish_and_clear();
result
}
pub fn with_progress<T>(
len: u64,
work: impl FnOnce(&indicatif::ProgressBar) -> Result<T, Error>,
) -> Result<T, Error> {
let bar = progress_bar(len);
let result = work(&bar);
bar.finish_and_clear();
result
}
pub fn plural(count: usize) -> &'static str {
if count == 1 { "" } else { "s" }
}
pub fn format_duration(duration: Duration) -> String {
let millis = duration.as_millis();
if millis < 1 {
"<1ms".to_string()
} else if millis < 1_000 {
format!("{millis}ms")
} else if millis < 60_000 {
format!("{}.{:02}s", millis / 1_000, millis % 1_000 / 10)
} else {
let secs = duration.as_secs();
format!("{}m {}s", secs / 60, secs % 60)
}
}
pub fn print_elapsed(duration: Duration) {
println!(
"{}",
format!("Done in {}", format_duration(duration)).dark_grey()
);
}
pub fn render_config() -> RenderConfig<'static> {
let (r, g, b) = ACCENT;
let accent = Color::rgb(r, g, b);
RenderConfig::default_colored()
.with_prompt_prefix(Styled::new("?").with_fg(Color::DarkGrey))
.with_answered_prompt_prefix(Styled::new("✓").with_fg(accent))
.with_text_input(StyleSheet::new().with_fg(Color::White))
.with_answer(StyleSheet::new().with_fg(accent))
.with_help_message(StyleSheet::new().with_fg(Color::DarkGrey))
.with_default_value(StyleSheet::new().with_fg(Color::DarkGrey))
.with_highlighted_option_prefix(Styled::new(">").with_fg(accent))
.with_selected_option(Some(StyleSheet::new().with_fg(accent)))
.with_error_message(
ErrorMessageRenderConfig::default_colored()
.with_prefix(Styled::new("✗").with_fg(accent))
.with_message(StyleSheet::new().with_fg(accent)),
)
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::format_duration;
#[test]
fn sub_millisecond_floors_to_less_than_one() {
assert_eq!(format_duration(Duration::ZERO), "<1ms");
assert_eq!(format_duration(Duration::from_micros(999)), "<1ms");
}
#[test]
fn milliseconds_up_to_a_second() {
assert_eq!(format_duration(Duration::from_millis(1)), "1ms");
assert_eq!(format_duration(Duration::from_millis(142)), "142ms");
assert_eq!(format_duration(Duration::from_millis(999)), "999ms");
}
#[test]
fn seconds_with_two_decimals() {
assert_eq!(format_duration(Duration::from_millis(1_000)), "1.00s");
assert_eq!(format_duration(Duration::from_millis(1_420)), "1.42s");
assert_eq!(format_duration(Duration::from_millis(59_999)), "59.99s");
}
#[test]
fn minutes_and_seconds() {
assert_eq!(format_duration(Duration::from_secs(60)), "1m 0s");
assert_eq!(format_duration(Duration::from_secs(72)), "1m 12s");
assert_eq!(format_duration(Duration::from_secs(3_599)), "59m 59s");
}
}