1#![allow(clippy::toplevel_ref_arg, clippy::uninlined_format_args)]
35
36use proc_macro::TokenStream;
37use std::io::Write;
38use std::process;
39use syn::{parse_macro_input, LitStr};
40use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
41
42#[proc_macro]
44pub fn yellow(input: TokenStream) -> TokenStream {
45 do_alert(Color::Yellow, input)
46}
47
48#[proc_macro]
50pub fn red(input: TokenStream) -> TokenStream {
51 do_alert(Color::Red, input)
52}
53
54fn do_alert(color: Color, input: TokenStream) -> TokenStream {
55 let message = parse_macro_input!(input as LitStr).value();
56
57 let ref mut stderr = StandardStream::stderr(ColorChoice::Auto);
58 let color_spec = ColorSpec::new().set_fg(Some(color)).clone();
59 let mut has_nonspace = false;
60 let mut says_error = false;
61
62 for mut line in message.lines() {
63 if !has_nonspace {
64 let (indent, heading, rest) = split_heading(line);
65 if let Some(indent) = indent {
66 let _ = write!(stderr, "{}", indent);
67 }
68 if let Some(heading) = heading {
69 let _ = stderr.set_color(color_spec.clone().set_bold(true));
70 let _ = write!(stderr, "{}", heading);
71 has_nonspace = true;
72 says_error = heading == "ERROR";
73 }
74 line = rest;
75 }
76 if line.is_empty() {
77 let _ = writeln!(stderr);
78 } else {
79 let _ = stderr.set_color(&color_spec);
80 let _ = writeln!(stderr, "{}", line);
81 has_nonspace = has_nonspace || line.contains(|ch: char| ch != ' ');
82 }
83 }
84
85 let _ = stderr.reset();
86 let _ = writeln!(stderr);
87
88 if color == Color::Red && says_error {
89 process::exit(1);
90 } else {
91 TokenStream::new()
92 }
93}
94
95fn split_heading(s: &str) -> (Option<&str>, Option<&str>, &str) {
96 let mut start = 0;
97 while start < s.len() && s[start..].starts_with(' ') {
98 start += 1;
99 }
100
101 let mut end = start;
102 while end < s.len() && s[end..].starts_with(|ch: char| ch.is_ascii_uppercase()) {
103 end += 1;
104 }
105
106 if end - start >= 3 && (end == s.len() || s[end..].starts_with(':')) {
107 let indent = (start > 0).then_some(&s[..start]);
108 let heading = &s[start..end];
109 let rest = &s[end..];
110 (indent, Some(heading), rest)
111 } else {
112 (None, None, s)
113 }
114}