Skip to main content

uv_warnings/
lib.rs

1use std::error::Error;
2use std::fmt;
3use std::sync::atomic::AtomicBool;
4use std::sync::{LazyLock, Mutex};
5
6// macro hygiene: The user might not have direct dependencies on those crates
7#[doc(hidden)]
8pub use anstream;
9#[doc(hidden)]
10pub use owo_colors;
11use rustc_hash::FxHashSet;
12use uv_errors::{ErrorOptions, Hints, write_error_chain_with_options};
13
14/// Whether user-facing warnings are enabled.
15pub static ENABLED: AtomicBool = AtomicBool::new(false);
16
17/// Enable user-facing warnings.
18pub fn enable() {
19    ENABLED.store(true, std::sync::atomic::Ordering::Relaxed);
20}
21
22/// Disable user-facing warnings.
23pub fn disable() {
24    ENABLED.store(false, std::sync::atomic::Ordering::Relaxed);
25}
26
27/// Format a warning chain to standard error.
28pub fn write_warning_chain(err: &dyn Error, hints: Hints<'_>) -> fmt::Result {
29    write_warning_chain_with_options(err, hints, ErrorOptions::default())
30}
31
32fn write_warning_chain_with_options<C, W: fmt::Write>(
33    err: &dyn Error,
34    hints: Hints<'_>,
35    options: ErrorOptions<'_, C, W>,
36) -> fmt::Result {
37    write_error_chain_with_options(
38        err,
39        hints,
40        options
41            .with_level("warning")
42            .with_color(owo_colors::AnsiColors::Yellow),
43    )
44}
45
46/// Warn a user, if warnings are enabled.
47#[macro_export]
48macro_rules! warn_user {
49    ($($arg:tt)*) => {{
50        use $crate::anstream::eprintln;
51        use $crate::owo_colors::OwoColorize;
52
53        if $crate::ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
54            let message = format!("{}", format_args!($($arg)*));
55            let formatted = message.bold();
56            eprintln!("{}{} {formatted}", "warning".yellow().bold(), ":".bold());
57        }
58    }};
59}
60
61pub static WARNINGS: LazyLock<Mutex<FxHashSet<String>>> = LazyLock::new(Mutex::default);
62
63/// Warn a user once, if warnings are enabled, with uniqueness determined by the content of the
64/// message.
65#[macro_export]
66macro_rules! warn_user_once {
67    ($($arg:tt)*) => {{
68        use $crate::anstream::eprintln;
69        use $crate::owo_colors::OwoColorize;
70
71        if $crate::ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
72            if let Ok(mut states) = $crate::WARNINGS.lock() {
73                let message = format!("{}", format_args!($($arg)*));
74                if states.insert(message.clone()) {
75                    eprintln!("{}{} {}", "warning".yellow().bold(), ":".bold(), message.bold());
76                }
77            }
78        }
79    }};
80}
81
82#[cfg(test)]
83mod tests {
84    use anyhow::anyhow;
85    use insta::assert_snapshot;
86    use uv_errors::{ErrorOptions, Hints};
87
88    use super::write_warning_chain_with_options;
89
90    #[test]
91    fn format_warning_chain() {
92        let error = anyhow!("Failed to create registry entry");
93        let mut output = String::new();
94        write_warning_chain_with_options(
95            error.as_ref(),
96            Hints::none(),
97            ErrorOptions::default().with_stream(&mut output),
98        )
99        .unwrap();
100        assert_snapshot!(format!("{output:?}"), @r#""\u{1b}[1m\u{1b}[33mwarning\u{1b}[39m\u{1b}[0m\u{1b}[1m:\u{1b}[0m Failed to create registry entry\n""#);
101        let output = anstream::adapter::strip_str(&output);
102
103        assert_snapshot!(output, @"warning: Failed to create registry entry
104");
105    }
106}