1use std::io::{self, IsTerminal};
2use std::path::Path;
3
4use anyhow::Error;
5use console::style;
6
7pub fn print_path(path: &Path) {
8 println!("{}", path.display());
9}
10
11pub fn print_success(message: &str) {
12 println!(
13 "{}",
14 style_stdout(message, |text| style(text).green().bold().to_string())
15 );
16}
17
18pub fn print_info(message: &str) {
19 println!(
20 "{}",
21 style_stdout(message, |text| style(text).cyan().to_string())
22 );
23}
24
25pub fn print_section(message: &str) {
26 println!(
27 "{}",
28 style_stdout(message, |text| style(text).green().bold().to_string())
29 );
30}
31
32pub fn print_list_entry(prefix: &str, message: &str, current: bool) {
33 let marker = if current {
34 style_stdout(prefix, |text| style(text).green().bold().to_string())
35 } else {
36 prefix.to_owned()
37 };
38 println!("{marker} {message}");
39}
40
41pub fn print_error(error: &Error) {
42 let mut chain = error.chain();
43 let Some(first) = chain.next() else {
44 return;
45 };
46
47 let first_message = clean_message(&first.to_string());
48 eprintln!(
49 "{} {}",
50 style_stderr("error:", |text| style(text).red().bold().to_string()),
51 first_message
52 );
53
54 let mut previous = first_message;
55 for cause in chain {
56 let message = clean_message(&cause.to_string());
57 if message.is_empty() || message == previous {
58 continue;
59 }
60 eprintln!(
61 " {} {}",
62 style_stderr("caused by:", |text| style(text).yellow().to_string()),
63 message
64 );
65 previous = message;
66 }
67}
68
69pub fn clean_message(message: &str) -> String {
70 let mut current = message.trim();
71
72 loop {
73 let lowered = current.to_ascii_lowercase();
74 if let Some(rest) = lowered.strip_prefix("error:") {
75 let index = current.len() - rest.len();
76 current = current[index..].trim();
77 continue;
78 }
79 if let Some(rest) = lowered.strip_prefix("fatal:") {
80 let index = current.len() - rest.len();
81 current = current[index..].trim();
82 continue;
83 }
84 break;
85 }
86
87 current.to_owned()
88}
89
90fn style_stdout<F>(message: &str, style: F) -> String
91where
92 F: FnOnce(&str) -> String,
93{
94 if io::stdout().is_terminal() {
95 style(message)
96 } else {
97 message.to_owned()
98 }
99}
100
101fn style_stderr<F>(message: &str, style: F) -> String
102where
103 F: FnOnce(&str) -> String,
104{
105 if io::stderr().is_terminal() {
106 style(message)
107 } else {
108 message.to_owned()
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn removes_error_prefixes() {
118 assert_eq!(
119 clean_message("error: No such remote 'origin'"),
120 "No such remote 'origin'"
121 );
122 assert_eq!(clean_message("fatal: bad revision"), "bad revision");
123 assert_eq!(clean_message("error: fatal: bad revision"), "bad revision");
124 }
125}