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