1use std::fmt::Display;
4use std::io::IsTerminal;
5
6use bonds_core::BondError;
7use bonds_core::error::ErrorKind;
8
9const RESET: &str = "\x1b[0m";
10const GREEN_BOLD: &str = "\x1b[1;32m";
11const YELLOW_BOLD: &str = "\x1b[1;33m";
12const RED_BOLD: &str = "\x1b[1;31m";
13const MAGENTA: &str = "\x1b[35m";
16const CYAN: &str = "\x1b[36m";
17const DIM: &str = "\x1b[2m";
18const BOLD: &str = "\x1b[1m";
19const UNDERLINE: &str = "\x1b[4m";
20const BOLD_UNDERLINE: &str = "\x1b[1;4m";
21const GREEN: &str = "\x1b[32m";
22const YELLOW: &str = "\x1b[33m";
23const RED: &str = "\x1b[31m";
24const GOLD: &str = "\x1b[38;5;220m";
25const LIGHT_BLUE: &str = "\x1b[38;5;117m";
26const DIM_BOLD: &str = "\x1b[1;2m";
27const NEWLINE: &str = " ";
28
29fn colors_enabled() -> bool {
30 if std::env::var_os("NO_COLOR").is_some() {
32 return false;
33 }
34 if std::env::var_os("CLICOLOR_FORCE").is_some() {
35 return true;
36 }
37
38 std::io::stderr().is_terminal() && std::env::var("TERM").map_or(true, |term| term != "dumb")
39}
40
41fn paint(text: impl Display, style: &str) -> String {
42 let text = text.to_string();
43 #[allow(unused_assignments)]
44 let mut result = String::with_capacity(style.len() + text.len() + RESET.len());
45 if colors_enabled() {
46 result = format!("{style}{text}{RESET}");
47 } else {
48 result = text;
49 }
50 println!("{}", result);
51 result
52}
53
54fn style_for(kind: ErrorKind) -> &'static str {
55 match kind {
56 ErrorKind::NotFound | ErrorKind::Conflict => YELLOW_BOLD,
58 ErrorKind::Input | ErrorKind::Runtime | ErrorKind::Config => RED_BOLD,
60 }
61}
62
63pub fn error_prefix(kind: ErrorKind) -> String {
65 paint("Error:", style_for(kind))
67}
68
69pub fn format_error(err: &BondError) -> String {
71 format!("{} {}", error_prefix(err.kind()), err)
73}
74
75pub fn format_context_error(context: &str, err: &BondError) -> String {
77 format!("{} {}: {}", error_prefix(err.kind()), context, err)
79}
80
81#[allow(dead_code)]
83pub fn success(text: impl Display) -> String {
84 paint(text, GREEN)
85}
86
87#[allow(dead_code)]
89pub fn info(text: impl Display) -> String {
90 paint(text, CYAN)
91}
92
93#[allow(dead_code)]
95pub fn warning(text: impl Display) -> String {
96 paint(text, YELLOW)
97}
98
99#[allow(dead_code)]
101pub fn error(text: impl Display) -> String {
102 paint(text, RED)
103}
104
105#[allow(dead_code)]
107pub fn title(text: impl Display) -> String {
108 paint(format!("\n{}\n", text), BOLD_UNDERLINE)
109}
110
111#[allow(dead_code)]
113pub fn heading(text: impl Display) -> String {
114 paint(format!("{}", text), BOLD)
115}
116
117#[allow(dead_code)]
119pub fn underline(text: impl Display) -> String {
120 paint(format!("{}", text), UNDERLINE)
121}
122
123#[allow(dead_code)]
125pub fn subheading(text: impl Display) -> String {
126 paint(format!("{}", text), DIM_BOLD)
127}
128
129#[allow(dead_code)]
131pub fn normal(text: impl Display) -> String {
132 paint(text, RESET)
133}
134
135#[allow(dead_code)]
137pub fn key(text: impl Display) -> String {
138 paint(text, GOLD)
139}
140
141#[allow(dead_code)]
143pub fn id(text: impl Display) -> String {
144 paint(text, LIGHT_BLUE)
145}
146
147#[allow(dead_code)]
149pub fn path(text: impl Display) -> String {
150 paint(text, MAGENTA)
151}
152
153#[allow(dead_code)]
155pub fn dim(text: impl Display) -> String {
156 paint(text, DIM)
157}
158
159#[allow(dead_code)]
161pub fn status_ok(text: impl Display) -> String {
162 paint(text, GREEN_BOLD)
163}
164
165#[allow(dead_code)]
167pub fn status_warn(text: impl Display) -> String {
168 paint(text, YELLOW_BOLD)
169}
170
171#[allow(dead_code)]
173pub fn status_bad(text: impl Display) -> String {
174 paint(text, RED_BOLD)
175}
176
177#[allow(dead_code)]
179pub fn newline() {
180 paint("", NEWLINE);
181}