use inquire::ui::{Attributes, Color, RenderConfig, StyleSheet, Styled};
use owo_colors::OwoColorize;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
static NO_COLOR_FLAG: AtomicBool = AtomicBool::new(false);
static NO_COLOR_ENV: OnceLock<bool> = OnceLock::new();
pub fn no_color() -> bool {
NO_COLOR_FLAG.load(Ordering::Relaxed)
|| *NO_COLOR_ENV.get_or_init(|| std::env::var("NO_COLOR").is_ok())
}
pub fn set_no_color() {
NO_COLOR_FLAG.store(true, Ordering::Relaxed);
}
fn teal_styled(s: &str, styled: bool) -> String {
if styled {
s.truecolor(0, 150, 136).to_string()
} else {
s.to_string()
}
}
fn orange_styled(s: &str, styled: bool) -> String {
if styled {
s.truecolor(255, 111, 0).to_string()
} else {
s.to_string()
}
}
fn purple_styled(s: &str, styled: bool) -> String {
if styled {
s.truecolor(138, 77, 210).to_string()
} else {
s.to_string()
}
}
fn green_styled(s: &str, styled: bool) -> String {
if styled {
s.truecolor(76, 175, 80).to_string()
} else {
s.to_string()
}
}
fn dim_styled(s: &str, styled: bool) -> String {
if styled {
s.dimmed().to_string()
} else {
s.to_string()
}
}
fn bold_styled(s: &str, styled: bool) -> String {
if styled {
s.bold().to_string()
} else {
s.to_string()
}
}
pub fn teal(s: &str) -> String {
teal_styled(s, !no_color())
}
pub fn orange(s: &str) -> String {
orange_styled(s, !no_color())
}
pub fn purple(s: &str) -> String {
purple_styled(s, !no_color())
}
pub fn green(s: &str) -> String {
green_styled(s, !no_color())
}
pub fn dim(s: &str) -> String {
dim_styled(s, !no_color())
}
pub fn bold(s: &str) -> String {
bold_styled(s, !no_color())
}
pub fn print_banner() {
println!();
println!(" {}", teal(r" _ _ ___ ___ ___"));
println!(" {}", teal(r"| \| | / __| | __| | \"));
println!(" {}", teal(r"| .` | \__ \ | _| | |) |"));
println!(" {}", teal(r"|_|\_| |___/ |___| |___/"));
println!();
println!(" {}", dim("N-Way Self-Evaluating Deliberation"));
println!(" {}", purple("Interactive Setup Wizard"));
println!();
}
fn format_section(title: &str) -> String {
let width = 42usize.saturating_sub(title.len() + 1);
let dashes = "─".repeat(width);
dim(&format!(" ── {title} {dashes}"))
}
pub fn section(title: &str) {
println!("\n{}", format_section(title));
}
fn format_success(msg: &str) -> String {
format!(" {} {}", teal("✓"), msg)
}
pub fn success(msg: &str) {
println!("{}", format_success(msg));
}
pub fn warn(msg: &str) {
println!(" {} {}", orange("⚠"), orange(msg));
}
pub fn info(msg: &str) {
println!(" {} {}", dim("·"), dim(msg));
}
fn render_config_styled(styled: bool) -> RenderConfig<'static> {
if !styled {
return RenderConfig::default();
}
let teal_colour = Color::AnsiValue(37);
RenderConfig::default()
.with_prompt_prefix(
Styled::new("❯").with_style_sheet(StyleSheet::default().with_fg(teal_colour)),
)
.with_highlighted_option_prefix(
Styled::new("❯").with_style_sheet(StyleSheet::default().with_fg(teal_colour)),
)
.with_selected_checkbox(
Styled::new("◉").with_style_sheet(
StyleSheet::default()
.with_fg(teal_colour)
.with_attr(Attributes::BOLD),
),
)
.with_answer(
StyleSheet::default()
.with_fg(teal_colour)
.with_attr(Attributes::BOLD),
)
}
pub fn render_config() -> RenderConfig<'static> {
render_config_styled(!no_color())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_no_color_stores_atomic_flag() {
set_no_color();
assert!(NO_COLOR_FLAG.load(Ordering::Relaxed));
assert!(no_color());
}
#[test]
fn no_color_returns_bool() {
let a = no_color();
let b = no_color();
assert_eq!(a, b, "no_color() should be deterministic");
}
#[test]
fn color_helpers_preserve_input_text() {
assert!(teal("hello").contains("hello"));
assert!(orange("warn").contains("warn"));
assert!(purple("meta").contains("meta"));
assert!(green("ok").contains("ok"));
assert!(dim("faint").contains("faint"));
assert!(bold("strong").contains("strong"));
}
#[test]
fn color_helpers_handle_empty_string() {
let t = teal("");
let d = dim("");
assert!(!t.contains("error"));
assert!(!d.contains("error"));
}
#[test]
fn color_helpers_handle_unicode() {
let emoji = "🔒 Locked";
let result = teal(emoji);
assert!(result.contains("Locked"));
}
#[test]
fn render_config_does_not_panic() {
let _rc = render_config();
}
#[test]
fn section_width_does_not_underflow() {
let long_title = "a".repeat(100);
section(&long_title);
}
#[test]
fn test_print_banner_does_not_panic() {
print_banner();
}
#[test]
fn test_success_does_not_panic() {
success("operation completed");
}
#[test]
fn test_warn_does_not_panic() {
warn("something may be wrong");
}
#[test]
fn test_info_does_not_panic() {
info("informational message");
}
#[test]
fn styled_helpers_plain_returns_exact_input() {
assert_eq!(teal_styled("abc", false), "abc");
assert_eq!(orange_styled("abc", false), "abc");
assert_eq!(purple_styled("abc", false), "abc");
assert_eq!(green_styled("abc", false), "abc");
assert_eq!(dim_styled("abc", false), "abc");
assert_eq!(bold_styled("abc", false), "abc");
}
#[test]
fn styled_helpers_colored_contains_ansi() {
let t = teal_styled("x", true);
assert!(t.contains("x"));
assert!(t.len() > 1, "styled output should have ANSI escapes");
let o = orange_styled("x", true);
assert!(o.contains("x"));
assert!(o.len() > 1);
let p = purple_styled("x", true);
assert!(p.contains("x"));
assert!(p.len() > 1);
let g = green_styled("x", true);
assert!(g.contains("x"));
assert!(g.len() > 1);
let d = dim_styled("x", true);
assert!(d.contains("x"));
assert!(d.len() > 1);
let b = bold_styled("x", true);
assert!(b.contains("x"));
assert!(b.len() > 1);
}
#[test]
fn render_config_styled_unstyled_returns_default() {
let _rc = render_config_styled(false);
}
#[test]
fn render_config_styled_with_colour() {
let _rc = render_config_styled(true);
}
#[test]
fn format_section_contains_title() {
let s = format_section("providers");
assert!(s.contains("providers"));
assert!(s.contains("──"));
}
#[test]
fn format_section_empty_title() {
let s = format_section("");
assert!(s.contains("──"));
}
#[test]
fn format_success_contains_checkmark_and_message() {
let s = format_success("it worked");
assert!(s.contains("it worked"));
}
}