1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
//! [![github]](https://github.com/dtolnay/build-alert) [![crates-io]](https://crates.io/crates/build-alert) [![docs-rs]](https://docs.rs/build-alert)
//!
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
//!
//! <br>
//!
//! Display a message in the Cargo build output during compilation.
//!
//! # Example
//!
//! ```
//! #[cfg(debug_assertions)]
//! build_alert::yellow! {"
//! NOTE: use --release
//! Syn's test suite has some tests that run on every source file
//! and test case in the rust-lang/rust repo, which can be pretty
//! slow in debug mode. Consider running cargo test with `--release`
//! to speed things up.
//! "}
//!
//! #[cfg(not(feature = "all-features"))]
//! build_alert::red! {"
//! ERROR: use --all-features
//! Syn's test suite normally only works with all-features enabled.
//! Run again with `--all-features`, or run with `--features test`
//! to bypass this check.
//! "}
//! ```
//!
//! ![screenshot](https://user-images.githubusercontent.com/1940490/227811885-3eca7b65-0425-4be5-aa1a-cf52d8014817.png)
#![allow(clippy::toplevel_ref_arg, clippy::uninlined_format_args)]
use proc_macro::TokenStream;
use std::io::Write;
use std::process;
use syn::{parse_macro_input, LitStr};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
/// For <kbd>NOTE:</kbd> or <kbd>WARNING:</kbd> alerts.
#[proc_macro]
pub fn yellow(input: TokenStream) -> TokenStream {
do_alert(Color::Yellow, input)
}
/// For <kbd>ERROR:</kbd> alerts.
#[proc_macro]
pub fn red(input: TokenStream) -> TokenStream {
do_alert(Color::Red, input)
}
fn do_alert(color: Color, input: TokenStream) -> TokenStream {
let message = parse_macro_input!(input as LitStr).value();
let ref mut stderr = StandardStream::stderr(ColorChoice::Auto);
let color_spec = ColorSpec::new().set_fg(Some(color)).clone();
let mut has_nonspace = false;
let mut says_error = false;
for mut line in message.lines() {
if !has_nonspace {
let (indent, heading, rest) = split_heading(line);
if let Some(indent) = indent {
let _ = write!(stderr, "{}", indent);
}
if let Some(heading) = heading {
let _ = stderr.set_color(color_spec.clone().set_bold(true));
let _ = write!(stderr, "{}", heading);
has_nonspace = true;
says_error = heading == "ERROR";
}
line = rest;
}
if line.is_empty() {
let _ = writeln!(stderr);
} else {
let _ = stderr.set_color(&color_spec);
let _ = writeln!(stderr, "{}", line);
has_nonspace = has_nonspace || line.contains(|ch: char| ch != ' ');
}
}
let _ = stderr.reset();
let _ = writeln!(stderr);
if color == Color::Red && says_error {
process::exit(1);
} else {
TokenStream::new()
}
}
fn split_heading(s: &str) -> (Option<&str>, Option<&str>, &str) {
let mut start = 0;
while start < s.len() && s[start..].starts_with(' ') {
start += 1;
}
let mut end = start;
while end < s.len() && s[end..].starts_with(|ch: char| ch.is_ascii_uppercase()) {
end += 1;
}
if end - start >= 3 && (end == s.len() || s[end..].starts_with(':')) {
let indent = (start > 0).then_some(&s[..start]);
let heading = &s[start..end];
let rest = &s[end..];
(indent, Some(heading), rest)
} else {
(None, None, s)
}
}