error_stack/fmt/
charset.rs

1use crate::{
2    fmt::r#override::{AtomicOverride, AtomicPreference},
3    Report,
4};
5
6/// The available supported charsets
7///
8/// Can be accessed through [`crate::fmt::HookContext::charset`], and set via
9/// [`Report::set_charset`].
10#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
11pub enum Charset {
12    /// Terminal of the user supports utf-8
13    ///
14    /// This is the default if no charset has been explicitly set.
15    // we assume that most fonts and terminals nowadays support Utf8, which is why this is
16    // the default
17    #[default]
18    Utf8,
19
20    /// Terminal of the user supports ASCII
21    Ascii,
22}
23
24impl Charset {
25    pub(super) fn load() -> Self {
26        CHARSET_OVERRIDE.load()
27    }
28}
29
30/// Value layout:
31/// `0x00`: `Charset::Ascii`
32/// `0x01`: `Charset::Utf8`
33///
34/// all others: default to [`Self::default`]
35impl AtomicPreference for Charset {
36    fn from_u8(value: u8) -> Self {
37        match value {
38            0x00 => Self::Ascii,
39            0x01 => Self::Utf8,
40            _ => Self::default(),
41        }
42    }
43
44    fn into_u8(self) -> u8 {
45        match self {
46            Self::Ascii => 0x00,
47            Self::Utf8 => 0x01,
48        }
49    }
50}
51
52static CHARSET_OVERRIDE: AtomicOverride<Charset> = AtomicOverride::new();
53
54impl Report<()> {
55    /// Set the charset preference
56    ///
57    /// The value defaults to [`Charset::Utf8`].
58    ///
59    /// # Example
60    ///
61    /// ```rust
62    /// # // we only test the snapshot on nightly, therefore report is unused (so is render)
63    /// # #![cfg_attr(not(nightly), allow(dead_code, unused_variables, unused_imports))]
64    /// use std::io::{Error, ErrorKind};
65    ///
66    /// use error_stack::{report, Report};
67    /// use error_stack::fmt::{Charset};
68    ///
69    /// struct Suggestion(&'static str);
70    ///
71    /// Report::install_debug_hook::<Suggestion>(|Suggestion(value), context| {
72    ///     match context.charset() {
73    ///         Charset::Utf8 => context.push_body(format!("📝 {value}")),
74    ///         Charset::Ascii => context.push_body(format!("suggestion: {value}"))
75    ///     };
76    /// });
77    ///
78    /// let report =
79    ///     report!(Error::from(ErrorKind::InvalidInput)).attach(Suggestion("oh no, try again"));
80    ///
81    /// # fn render(value: String) -> String {
82    /// #     let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?:  .*\n)*  .*").unwrap();
83    /// #     let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap();
84    /// #
85    /// #     let value = backtrace.replace_all(&value, "backtrace no. $1\n  [redacted]");
86    /// #     let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)");
87    /// #
88    /// #     ansi_to_html::convert(value.as_ref()).unwrap()
89    /// # }
90    /// #
91    /// Report::set_charset(Charset::Utf8);
92    /// println!("{report:?}");
93    /// # #[cfg(nightly)]
94    /// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__charset_utf8.snap")].assert_eq(&render(format!("{report:?}")));
95    ///
96    /// Report::set_charset(Charset::Ascii);
97    /// println!("{report:?}");
98    /// # #[cfg(nightly)]
99    /// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__charset_ascii.snap")].assert_eq(&render(format!("{report:?}")));
100    /// ```
101    ///
102    /// Which will result in something like:
103    ///
104    /// <pre>
105    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__charset_utf8.snap"))]
106    /// </pre>
107    ///
108    /// <pre>
109    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__charset_ascii.snap"))]
110    /// </pre>
111    pub fn set_charset(charset: Charset) {
112        CHARSET_OVERRIDE.store(charset);
113    }
114}