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