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";
19const CYAN: &str = "\x1b[36m";
20const DIM: &str = "\x1b[2m";
21const DIM_UNDERLINE: &str = "\x1b[2;4m";
22const BOLD: &str = "\x1b[1m";
23const UNDERLINE: &str = "\x1b[4m";
24const BOLD_UNDERLINE: &str = "\x1b[1;4m";
25const GREEN: &str = "\x1b[32m";
26const YELLOW: &str = "\x1b[33m";
27const RED: &str = "\x1b[31m";
28const GOLD: &str = "\x1b[38;5;220m";
29const LIGHT_BLUE: &str = "\x1b[38;5;117m";
30const DIM_BOLD: &str = "\x1b[1;2m";
31const NEWLINE: &str = " ";
32
33const BONDS_BANNER: &str = r#"
36 __ __
37/\ \ /\ \
38\ \ \____ ___ ___ \_\ \ ____
39 \ \ '__`\ / __`\/' _ `\ /'_` \ /',__\
40 \ \ \L\ /\ \L\ /\ \/\ \/\ \L\ \/\__, `\
41 \ \_,__\ \____\ \_\ \_\ \___,_\/\____/
42 \/___/ \/___/ \/_/\/_/\/__,_ /\/___/
43"#;
44
45pub fn landing(version: &str) {
48 paint(BONDS_BANNER, BOLD);
49 paint(" ", DIM_UNDERLINE);
50 newline();
51 paint(" https://bonds.fyi ", LIGHT_BLUE);
52 paint(" ", DIM_UNDERLINE);
53 newline();
54 info(format!("Version {version}"));
55 dim("Organize and manage source-target directory bonds.");
56 newline();
57
58 heading("Quick start:");
59 normal(" bond add <source> [target] [--name <name>] [--dry-run] [--verbose]");
60 normal(" bond list");
61 normal(" bond info <id|name>");
62 normal(" bond remove <id|name> [--with-target] [--dry-run] [--verbose]");
63 normal(
64 " bond update <id|name> [--source <path>] [--target <path>] [--name <name>] [--dry-run] [--verbose]",
65 );
66 normal(" bond migrate <id|name> [dest] [--dry-run] [--verbose]");
67 newline();
68
69 dim("Use `bond --help` for the full command reference.");
70}
71
72fn colors_enabled() -> bool {
73 if std::env::var_os("NO_COLOR").is_some() {
75 return false;
76 }
77 if std::env::var_os("CLICOLOR_FORCE").is_some() {
78 return true;
79 }
80
81 std::io::stderr().is_terminal() && std::env::var("TERM").map_or(true, |term| term != "dumb")
82}
83
84fn paint(text: impl Display, style: &str) -> String {
85 let text = text.to_string();
86 #[allow(unused_assignments)]
87 let mut result = String::with_capacity(style.len() + text.len() + RESET.len());
88 if colors_enabled() {
89 result = format!("{style}{text}{RESET}");
90 } else {
91 result = text;
92 }
93 println!("{}", result);
94 result
95}
96
97fn style_for(kind: ErrorKind) -> &'static str {
98 match kind {
99 ErrorKind::NotFound | ErrorKind::Conflict => YELLOW_BOLD,
101 ErrorKind::Input | ErrorKind::Runtime | ErrorKind::Config => RED_BOLD,
103 }
104}
105
106pub fn error_prefix(kind: ErrorKind) -> String {
108 paint("Error:", style_for(kind))
110}
111
112pub fn format_error(err: &BondError) -> String {
114 format!("{} {}", error_prefix(err.kind()), err)
116}
117
118pub fn format_context_error(context: &str, err: &BondError) -> String {
120 format!("{} {}: {}", error_prefix(err.kind()), context, err)
122}
123
124#[allow(dead_code)]
126pub fn success(text: impl Display) -> String {
127 paint(text, GREEN)
128}
129
130#[allow(dead_code)]
132pub fn info(text: impl Display) -> String {
133 paint(text, CYAN)
134}
135
136#[allow(dead_code)]
138pub fn warning(text: impl Display) -> String {
139 paint(text, YELLOW)
140}
141
142#[allow(dead_code)]
144pub fn error(text: impl Display) -> String {
145 paint(text, RED)
146}
147
148#[allow(dead_code)]
150pub fn title(text: impl Display) -> String {
151 paint(format!("\n{}\n", text), BOLD_UNDERLINE)
152}
153
154#[allow(dead_code)]
156pub fn heading(text: impl Display) -> String {
157 paint(format!("{}", text), BOLD)
158}
159
160#[allow(dead_code)]
162pub fn underline(text: impl Display) -> String {
163 paint(format!("{}", text), UNDERLINE)
164}
165
166#[allow(dead_code)]
168pub fn subheading(text: impl Display) -> String {
169 paint(format!("{}", text), DIM_BOLD)
170}
171
172#[allow(dead_code)]
174pub fn normal(text: impl Display) -> String {
175 paint(text, RESET)
176}
177
178#[allow(dead_code)]
180pub fn key(text: impl Display) -> String {
181 paint(text, GOLD)
182}
183
184#[allow(dead_code)]
186pub fn id(text: impl Display) -> String {
187 paint(text, LIGHT_BLUE)
188}
189
190#[allow(dead_code)]
192pub fn path(text: impl Display) -> String {
193 paint(text, MAGENTA)
194}
195
196#[allow(dead_code)]
198pub fn dim(text: impl Display) -> String {
199 paint(text, DIM)
200}
201
202#[allow(dead_code)]
204pub fn status_ok(text: impl Display) -> String {
205 paint(text, GREEN_BOLD)
206}
207
208#[allow(dead_code)]
210pub fn status_warn(text: impl Display) -> String {
211 paint(text, YELLOW_BOLD)
212}
213
214#[allow(dead_code)]
216pub fn status_bad(text: impl Display) -> String {
217 paint(text, RED_BOLD)
218}
219
220#[allow(dead_code)]
222pub fn newline() {
223 paint("", NEWLINE);
224}
225
226#[allow(dead_code)]
228pub fn debug(text: impl Display) -> String {
229 paint(text, DIM)
230}