annotate_snippets/level.rs
1//! [`Level`] constants for easy importing
2
3use alloc::borrow::Cow;
4
5use anstyle::Style;
6
7use crate::renderer::stylesheet::Stylesheet;
8use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT};
9use crate::{Message, OptionCow, Title};
10
11/// Default `error:` [`Level`]
12pub const ERROR: Level<'_> = Level {
13 name: None,
14 level: LevelInner::Error,
15};
16
17/// Default `warning:` [`Level`]
18pub const WARNING: Level<'_> = Level {
19 name: None,
20 level: LevelInner::Warning,
21};
22
23/// Default `info:` [`Level`]
24pub const INFO: Level<'_> = Level {
25 name: None,
26 level: LevelInner::Info,
27};
28
29/// Default `note:` [`Level`]
30pub const NOTE: Level<'_> = Level {
31 name: None,
32 level: LevelInner::Note,
33};
34
35/// Default `help:` [`Level`]
36pub const HELP: Level<'_> = Level {
37 name: None,
38 level: LevelInner::Help,
39};
40
41/// Severity level for [`Title`]s and [`Message`]s
42///
43/// # Example
44///
45/// ```rust
46/// # use annotate_snippets::*;
47/// let report = &[
48/// Level::ERROR.primary_title("mismatched types").id("E0308")
49/// .element(Level::NOTE.message("expected reference")),
50/// Group::with_title(
51/// Level::HELP.secondary_title("function defined here")
52/// ),
53/// ];
54/// ```
55#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
56pub struct Level<'a> {
57 pub(crate) name: Option<Option<Cow<'a, str>>>,
58 pub(crate) level: LevelInner,
59}
60
61/// # Constructors
62impl<'a> Level<'a> {
63 pub const ERROR: Level<'a> = ERROR;
64 pub const WARNING: Level<'a> = WARNING;
65 pub const INFO: Level<'a> = INFO;
66 pub const NOTE: Level<'a> = NOTE;
67 pub const HELP: Level<'a> = HELP;
68}
69
70impl<'a> Level<'a> {
71 /// For the primary, or root cause, [`Group`][crate::Group] (the first) in a [`Report`][crate::Report]
72 ///
73 /// See [`Group::with_title`][crate::Group::with_title]
74 ///
75 /// <div class="warning">
76 ///
77 /// Text passed to this function is considered "untrusted input", as such
78 /// all text is passed through a normalization function. Styled text is
79 /// not allowed to be passed to this function.
80 ///
81 /// </div>
82 pub fn primary_title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> {
83 Title {
84 level: self,
85 id: None,
86 text: text.into(),
87 allows_styling: false,
88 }
89 }
90
91 /// For any secondary, or context, [`Group`][crate::Group]s (subsequent) in a [`Report`][crate::Report]
92 ///
93 /// See [`Group::with_title`][crate::Group::with_title]
94 ///
95 /// <div class="warning">
96 ///
97 /// Text passed to this function is allowed to be styled, as such all
98 /// text is considered "trusted input" and has no normalizations applied to
99 /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be
100 /// used to normalize untrusted text before it is passed to this function.
101 ///
102 /// </div>
103 pub fn secondary_title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> {
104 Title {
105 level: self,
106 id: None,
107 text: text.into(),
108 allows_styling: true,
109 }
110 }
111
112 /// A text [`Element`][crate::Element] in a [`Group`][crate::Group]
113 ///
114 /// <div class="warning">
115 ///
116 /// Text passed to this function is allowed to be styled, as such all
117 /// text is considered "trusted input" and has no normalizations applied to
118 /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be
119 /// used to normalize untrusted text before it is passed to this function.
120 ///
121 /// </div>
122 pub fn message(self, text: impl Into<Cow<'a, str>>) -> Message<'a> {
123 Message {
124 level: self,
125 text: text.into(),
126 }
127 }
128
129 pub(crate) fn as_str(&'a self) -> &'a str {
130 match (&self.name, self.level) {
131 (Some(Some(name)), _) => name.as_ref(),
132 (Some(None), _) => "",
133 (None, LevelInner::Error) => ERROR_TXT,
134 (None, LevelInner::Warning) => WARNING_TXT,
135 (None, LevelInner::Info) => INFO_TXT,
136 (None, LevelInner::Note) => NOTE_TXT,
137 (None, LevelInner::Help) => HELP_TXT,
138 }
139 }
140
141 pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style {
142 self.level.style(stylesheet)
143 }
144}
145
146/// # Customize the `Level`
147impl<'a> Level<'a> {
148 /// Replace the name describing this [`Level`]
149 ///
150 /// <div class="warning">
151 ///
152 /// Text passed to this function is considered "untrusted input", as such
153 /// all text is passed through a normalization function. Pre-styled text is
154 /// not allowed to be passed to this function.
155 ///
156 /// </div>
157 ///
158 /// # Example
159 ///
160 /// ```rust
161 /// # #[allow(clippy::needless_doctest_main)]
162 #[doc = include_str!("../examples/custom_level.rs")]
163 /// ```
164 #[doc = include_str!("../examples/custom_level.svg")]
165 pub fn with_name(self, name: impl Into<OptionCow<'a>>) -> Level<'a> {
166 Level {
167 name: Some(name.into().0),
168 level: self.level,
169 }
170 }
171
172 /// Do not show the [`Level`]s name
173 ///
174 /// Useful for:
175 /// - Another layer of the application will include the level (e.g. when rendering errors)
176 /// - [`Message`]s that are part of a previous [`Group`][crate::Group] [`Element`][crate::Element]s
177 ///
178 /// # Example
179 ///
180 /// ```rust
181 /// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level};
182 ///let source = r#"fn main() {
183 /// let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types
184 /// let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types
185 /// }"#;
186 /// let report = &[
187 /// Level::ERROR.primary_title("mismatched types").id("E0308")
188 /// .element(
189 /// Snippet::source(source)
190 /// .path("$DIR/mismatched-types.rs")
191 /// .annotation(
192 /// AnnotationKind::Primary
193 /// .span(105..131)
194 /// .label("expected `&str`, found `&[u8; 0]`"),
195 /// )
196 /// .annotation(
197 /// AnnotationKind::Context
198 /// .span(98..102)
199 /// .label("expected due to this"),
200 /// ),
201 /// )
202 /// .element(
203 /// Level::NOTE
204 /// .no_name()
205 /// .message("expected reference `&str`\nfound reference `&'static [u8; 0]`"),
206 /// ),
207 /// ];
208 /// ```
209 pub fn no_name(self) -> Level<'a> {
210 self.with_name(None::<&str>)
211 }
212}
213
214#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
215pub(crate) enum LevelInner {
216 Error,
217 Warning,
218 Info,
219 Note,
220 Help,
221}
222
223impl LevelInner {
224 pub(crate) fn style(self, stylesheet: &Stylesheet) -> Style {
225 match self {
226 LevelInner::Error => stylesheet.error,
227 LevelInner::Warning => stylesheet.warning,
228 LevelInner::Info => stylesheet.info,
229 LevelInner::Note => stylesheet.note,
230 LevelInner::Help => stylesheet.help,
231 }
232 }
233}