1use std::env;
5use std::fmt;
6use std::io::{self, Write};
7use std::str::FromStr;
8
9use crate::errors::Result;
10use owo_colors::{self, OwoColorize};
11
12macro_rules! cross_prefix {
14 ($s:literal) => {
15 concat!("[cross]", " ", $s)
16 };
17}
18
19macro_rules! write_style {
21 ($stream:ident, $msg_info:expr, $message:expr $(, $style:ident)* $(,)?) => {{
22 match $msg_info.color_choice {
23 ColorChoice::Always => write!($stream, "{}", $message $(.$style())*),
24 ColorChoice::Never => write!($stream, "{}", $message),
25 ColorChoice::Auto => write!(
26 $stream,
27 "{}",
28 $message $(.if_supports_color($stream.owo(), |text| text.$style()))*
29 ),
30 }?;
31 }};
32}
33
34macro_rules! message {
36 (@status $stream:ident, $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{
40 write_style!($stream, $msg_info, $status, bold, $color);
41 write_style!($stream, $msg_info, ":", bold);
42 match $message {
43 Some(message) => writeln!($stream, " {}", message)?,
44 None => write!($stream, " ")?,
45 }
46
47 Ok(())
48 }};
49
50 (@status @name $name:ident, $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{
51 let mut stream = io::$name();
52 message!(@status stream, $status, $message, $color, $msg_info)
53 }};
54}
55
56macro_rules! status {
58 (@stderr $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{
59 message!(@status @name stderr, $status, $message, $color, $msg_info)
60 }};
61
62 (@stdout $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{
63 message!(@status @name stdout, $status, $message, $color, $msg_info)
64 }};
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Verbosity {
70 Quiet,
71 Normal,
72 Verbose,
73}
74
75impl Verbosity {
76 pub fn verbose(self) -> bool {
77 match self {
78 Self::Verbose => true,
79 Self::Normal | Self::Quiet => false,
80 }
81 }
82
83 fn create(color_choice: ColorChoice, verbose: bool, quiet: bool) -> Option<Self> {
84 match (verbose, quiet) {
85 (true, true) => {
86 MessageInfo::from(color_choice).fatal("cannot set both --verbose and --quiet", 101)
87 }
88 (true, false) => Some(Verbosity::Verbose),
89 (false, true) => Some(Verbosity::Quiet),
90 (false, false) => None,
91 }
92 }
93}
94
95impl Default for Verbosity {
96 fn default() -> Verbosity {
97 Verbosity::Normal
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum ColorChoice {
104 Always,
106 Never,
108 Auto,
110}
111
112impl FromStr for ColorChoice {
113 type Err = eyre::ErrReport;
114
115 fn from_str(s: &str) -> Result<ColorChoice> {
116 match s {
117 "always" => Ok(ColorChoice::Always),
118 "never" => Ok(ColorChoice::Never),
119 "auto" => Ok(ColorChoice::Auto),
120 arg => eyre::bail!(
121 "argument for --color must be auto, always, or never, but found `{arg}`"
122 ),
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct MessageInfo {
130 pub color_choice: ColorChoice,
131 pub verbosity: Verbosity,
132 pub stdout_needs_erase: bool,
133 pub stderr_needs_erase: bool,
134}
135
136impl MessageInfo {
137 pub const fn new(color_choice: ColorChoice, verbosity: Verbosity) -> MessageInfo {
138 MessageInfo {
139 color_choice,
140 verbosity,
141 stdout_needs_erase: false,
142 stderr_needs_erase: false,
143 }
144 }
145
146 pub fn create(verbose: bool, quiet: bool, color: Option<&str>) -> Result<MessageInfo> {
147 let color_choice = get_color_choice(color)?;
148 let verbosity = get_verbosity(color_choice, verbose, quiet)?;
149
150 Ok(MessageInfo {
151 color_choice,
152 verbosity,
153 stdout_needs_erase: false,
154 stderr_needs_erase: false,
155 })
156 }
157
158 #[must_use]
159 pub fn is_verbose(&self) -> bool {
160 self.verbosity.verbose()
161 }
162
163 fn as_verbosity<T, C: Fn(&mut MessageInfo) -> T>(&mut self, call: C, new: Verbosity) -> T {
164 let old = self.verbosity;
165 self.verbosity = new;
166 let result = call(self);
167 self.verbosity = old;
168
169 result
170 }
171
172 pub fn as_quiet<T, C: Fn(&mut MessageInfo) -> T>(&mut self, call: C) -> T {
173 self.as_verbosity(call, Verbosity::Quiet)
174 }
175
176 pub fn as_normal<T, C: Fn(&mut MessageInfo) -> T>(&mut self, call: C) -> T {
177 self.as_verbosity(call, Verbosity::Normal)
178 }
179
180 pub fn as_verbose<T, C: Fn(&mut MessageInfo) -> T>(&mut self, call: C) -> T {
181 self.as_verbosity(call, Verbosity::Verbose)
182 }
183
184 fn erase_line<S: Stream + Write>(&mut self, stream: &mut S) -> Result<()> {
185 stream.write_all(b"\x1B[K").map_err(Into::into)
187 }
188
189 fn stdout_check_erase(&mut self) -> Result<()> {
190 if self.stdout_needs_erase {
191 self.erase_line(&mut io::stdout())?;
192 self.stdout_needs_erase = false;
193 }
194 Ok(())
195 }
196
197 fn stderr_check_erase(&mut self) -> Result<()> {
198 if self.stderr_needs_erase {
199 self.erase_line(&mut io::stderr())?;
200 self.stderr_needs_erase = false;
201 }
202 Ok(())
203 }
204
205 pub fn fatal<T: fmt::Display>(&mut self, message: T, code: i32) -> ! {
207 self.error(message)
208 .expect("could not display fatal message");
209 std::process::exit(code);
210 }
211
212 pub fn error<T: fmt::Display>(&mut self, message: T) -> Result<()> {
214 self.stderr_check_erase()?;
215 status!(@stderr cross_prefix!("error"), Some(&message), red, self)
216 }
217
218 pub fn warn<T: fmt::Display>(&mut self, message: T) -> Result<()> {
220 match self.verbosity {
221 Verbosity::Quiet => Ok(()),
222 _ => status!(@stderr
223 cross_prefix!("warning"),
224 Some(&message),
225 yellow,
226 self,
227 ),
228 }
229 }
230
231 pub fn note<T: fmt::Display>(&mut self, message: T) -> Result<()> {
233 match self.verbosity {
234 Verbosity::Quiet => Ok(()),
235 _ => status!(@stderr cross_prefix!("note"), Some(&message), cyan, self),
236 }
237 }
238
239 pub fn status<T: fmt::Display>(&mut self, message: T) -> Result<()> {
240 match self.verbosity {
241 Verbosity::Quiet => Ok(()),
242 _ => {
243 eprintln!("{}", message);
244 Ok(())
245 }
246 }
247 }
248
249 pub fn print<T: fmt::Display>(&mut self, message: T) -> Result<()> {
251 self.stdout_check_erase()?;
252 println!("{}", message);
253 Ok(())
254 }
255
256 pub fn info<T: fmt::Display>(&mut self, message: T) -> Result<()> {
258 match self.verbosity {
259 Verbosity::Quiet => Ok(()),
260 _ => {
261 println!("{}", message);
262 Ok(())
263 }
264 }
265 }
266
267 pub fn debug<T: fmt::Display>(&mut self, message: T) -> Result<()> {
269 match self.verbosity {
270 Verbosity::Quiet | Verbosity::Normal => Ok(()),
271 _ => {
272 println!("{}", message);
273 Ok(())
274 }
275 }
276 }
277
278 pub fn fatal_usage<T: fmt::Display>(
279 &mut self,
280 arg: T,
281 provided: Option<&str>,
282 possible: Option<&[&str]>,
283 code: i32,
284 ) -> ! {
285 self.error_usage(arg, provided, possible)
286 .expect("could not display usage message");
287 std::process::exit(code);
288 }
289
290 fn error_usage<T: fmt::Display>(
291 &mut self,
292 arg: T,
293 provided: Option<&str>,
294 possible: Option<&[&str]>,
295 ) -> Result<()> {
296 let mut stream = io::stderr();
297 write_style!(stream, self, cross_prefix!("error"), bold, red);
298 write_style!(stream, self, ":", bold);
299 match provided {
300 Some(value) => {
301 write_style!(
302 stream,
303 self,
304 format_args!(" \"{value}\" isn't a valid value for '")
305 );
306 write_style!(stream, self, arg, yellow);
307 write_style!(stream, self, "'\n");
308 }
309 None => {
310 write_style!(stream, self, " The argument '");
311 write_style!(stream, self, arg, yellow);
312 write_style!(stream, self, "' requires a value but none was supplied\n");
313 }
314 }
315 match possible {
316 Some(values) if !values.is_empty() => {
317 let error_indent = cross_prefix!("error: ").len();
318 write_style!(
319 stream,
320 self,
321 format_args!("{:error_indent$}[possible values: ", "")
322 );
323 let max_index = values.len() - 1;
324 for (index, value) in values.iter().enumerate() {
325 write_style!(stream, self, value, green);
326 if index < max_index {
327 write_style!(stream, self, ", ");
328 }
329 }
330 write_style!(stream, self, "]\n");
331 }
332 _ => (),
333 }
334 write_style!(stream, self, "Usage:\n");
335 write_style!(
336 stream,
337 self,
338 " cross [+toolchain] [OPTIONS] [SUBCOMMAND]\n"
339 );
340 write_style!(stream, self, "\n");
341 write_style!(stream, self, "For more information try ");
342 write_style!(stream, self, "--help", green);
343 write_style!(stream, self, "\n");
344
345 stream.flush()?;
346
347 Ok(())
348 }
349}
350
351impl Default for MessageInfo {
352 fn default() -> MessageInfo {
353 MessageInfo::new(ColorChoice::Auto, Verbosity::Normal)
354 }
355}
356
357impl From<ColorChoice> for MessageInfo {
358 fn from(color_choice: ColorChoice) -> MessageInfo {
359 MessageInfo::new(color_choice, Verbosity::Normal)
360 }
361}
362
363impl From<Verbosity> for MessageInfo {
364 fn from(verbosity: Verbosity) -> MessageInfo {
365 MessageInfo::new(ColorChoice::Auto, verbosity)
366 }
367}
368
369impl From<(ColorChoice, Verbosity)> for MessageInfo {
370 fn from(info: (ColorChoice, Verbosity)) -> MessageInfo {
371 MessageInfo::new(info.0, info.1)
372 }
373}
374
375pub fn cargo_envvar_bool(var: &str) -> Result<bool> {
377 match env::var(var).ok() {
378 Some(value) => value.parse::<bool>().map_err(|_ignore| {
379 eyre::eyre!("environment variable for `{var}` was not `true` or `false`.")
380 }),
381 None => Ok(false),
382 }
383}
384
385pub fn invalid_color(provided: Option<&str>) -> ! {
386 let possible = ["auto", "always", "never"];
387 MessageInfo::default().fatal_usage("--color <WHEN>", provided, Some(&possible), 1);
388}
389
390fn get_color_choice(color: Option<&str>) -> Result<ColorChoice> {
391 Ok(match color {
392 Some(arg) => arg.parse().unwrap_or_else(|_| invalid_color(color)),
393 None => match env::var("CARGO_TERM_COLOR").ok().as_deref() {
394 Some(arg) => arg.parse().unwrap_or_else(|_| invalid_color(color)),
395 None => ColorChoice::Auto,
396 },
397 })
398}
399
400fn get_verbosity(color_choice: ColorChoice, verbose: bool, quiet: bool) -> Result<Verbosity> {
401 let env_verbose = cargo_envvar_bool("CARGO_TERM_VERBOSE")?;
403 let env_quiet = cargo_envvar_bool("CARGO_TERM_QUIET")?;
404 Ok(match Verbosity::create(color_choice, verbose, quiet) {
405 Some(v) => v,
406 None => Verbosity::create(color_choice, env_verbose, env_quiet).unwrap_or_default(),
407 })
408}
409
410pub trait Stream {
411 const TTY: atty::Stream;
412 const OWO: owo_colors::Stream;
413
414 #[must_use]
415 fn is_atty() -> bool {
416 atty::is(Self::TTY)
417 }
418
419 fn owo(&self) -> owo_colors::Stream {
420 Self::OWO
421 }
422}
423
424impl Stream for io::Stdin {
425 const TTY: atty::Stream = atty::Stream::Stdin;
426 const OWO: owo_colors::Stream = owo_colors::Stream::Stdin;
427}
428
429impl Stream for io::Stdout {
430 const TTY: atty::Stream = atty::Stream::Stdout;
431 const OWO: owo_colors::Stream = owo_colors::Stream::Stdout;
432}
433
434impl Stream for io::Stderr {
435 const TTY: atty::Stream = atty::Stream::Stderr;
436 const OWO: owo_colors::Stream = owo_colors::Stream::Stderr;
437}
438
439pub fn default_ident() -> usize {
440 cross_prefix!("").len()
441}
442
443#[must_use]
444pub fn indent(message: &str, spaces: usize) -> String {
445 message
446 .lines()
447 .map(|s| format!("{:spaces$}{s}", ""))
448 .collect()
449}