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}