1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3
4mod source;
5mod display;
6mod draw;
7mod write;
8
9pub use crate::{
10 source::{Line, Source, Cache, FileCache, FnCache, sources},
11 draw::{Fmt, ColorGenerator},
12};
13pub use yansi::Color;
14
15#[cfg(any(feature = "concolor", doc))]
16pub use crate::draw::StdoutFmt;
17
18use crate::display::*;
19use std::{
20 ops::Range,
21 io::{self, Write},
22 hash::Hash,
23 cmp::{PartialEq, Eq},
24 fmt,
25};
26use unicode_width::UnicodeWidthChar;
27
28pub trait Span {
30 type SourceId: PartialEq + ToOwned + ?Sized;
32
33 fn source(&self) -> &Self::SourceId;
35
36 fn start(&self) -> usize;
40
41 fn end(&self) -> usize;
47
48 fn len(&self) -> usize { self.end().saturating_sub(self.start()) }
50
51 fn contains(&self, offset: usize) -> bool { (self.start()..self.end()).contains(&offset) }
53}
54
55impl Span for Range<usize> {
56 type SourceId = ();
57
58 fn source(&self) -> &Self::SourceId { &() }
59 fn start(&self) -> usize { self.start }
60 fn end(&self) -> usize { self.end }
61}
62
63impl<Id: fmt::Debug + Hash + PartialEq + Eq + ToOwned> Span for (Id, Range<usize>) {
64 type SourceId = Id;
65
66 fn source(&self) -> &Self::SourceId { &self.0 }
67 fn start(&self) -> usize { self.1.start }
68 fn end(&self) -> usize { self.1.end }
69}
70
71#[derive(Clone, Debug, Hash, PartialEq, Eq)]
73pub struct Label<S = Range<usize>> {
74 span: S,
75 msg: Option<String>,
76 color: Option<Color>,
77 order: i32,
78 priority: i32,
79}
80
81impl<S> Label<S> {
82 pub fn new(span: S) -> Self {
84 Self {
85 span,
86 msg: None,
87 color: None,
88 order: 0,
89 priority: 0,
90 }
91 }
92
93 pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
95 self.msg = Some(msg.to_string());
96 self
97 }
98
99 pub fn with_color(mut self, color: Color) -> Self {
101 self.color = Some(color);
102 self
103 }
104
105 pub fn with_order(mut self, order: i32) -> Self {
116 self.order = order;
117 self
118 }
119
120 pub fn with_priority(mut self, priority: i32) -> Self {
130 self.priority = priority;
131 self
132 }
133}
134
135pub struct Report<'a, S: Span = Range<usize>> {
137 kind: ReportKind<'a>,
138 code: Option<String>,
139 msg: Option<String>,
140 note: Option<String>,
141 help: Option<String>,
142 location: (<S::SourceId as ToOwned>::Owned, usize),
143 labels: Vec<Label<S>>,
144 config: Config,
145}
146
147impl<S: Span> Report<'_, S> {
148 pub fn build<Id: Into<<S::SourceId as ToOwned>::Owned>>(kind: ReportKind, src_id: Id, offset: usize) -> ReportBuilder<S> {
150 ReportBuilder {
151 kind,
152 code: None,
153 msg: None,
154 note: None,
155 help: None,
156 location: (src_id.into(), offset),
157 labels: Vec::new(),
158 config: Config::default(),
159 }
160 }
161
162 pub fn eprint<C: Cache<S::SourceId>>(&self, cache: C) -> io::Result<()> {
164 self.write(cache, io::stderr())
165 }
166
167 pub fn print<C: Cache<S::SourceId>>(&self, cache: C) -> io::Result<()> {
172 self.write_for_stdout(cache, io::stdout())
173 }
174}
175
176impl<'a, S: Span> fmt::Debug for Report<'a, S> {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 f.debug_struct("Report")
179 .field("kind", &self.kind)
180 .field("code", &self.code)
181 .field("msg", &self.msg)
182 .field("note", &self.note)
183 .field("help", &self.help)
184 .field("config", &self.config)
185 .finish()
186 }
187}
188#[derive(Copy, Clone, Debug, PartialEq, Eq)]
190pub enum ReportKind<'a> {
191 Error,
194 Warning,
197 Advice,
199 Custom(&'a str, Color),
201}
202
203impl fmt::Display for ReportKind<'_> {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 match self {
206 ReportKind::Error => write!(f, "Error"),
207 ReportKind::Warning => write!(f, "Warning"),
208 ReportKind::Advice => write!(f, "Advice"),
209 ReportKind::Custom(s, _) => write!(f, "{}", s),
210 }
211 }
212}
213
214pub struct ReportBuilder<'a, S: Span> {
216 kind: ReportKind<'a>,
217 code: Option<String>,
218 msg: Option<String>,
219 note: Option<String>,
220 help: Option<String>,
221 location: (<S::SourceId as ToOwned>::Owned, usize),
222 labels: Vec<Label<S>>,
223 config: Config,
224}
225
226impl<'a, S: Span> ReportBuilder<'a, S> {
227 pub fn with_code<C: fmt::Display>(mut self, code: C) -> Self {
229 self.code = Some(format!("{:02}", code));
230 self
231 }
232
233 pub fn set_message<M: ToString>(&mut self, msg: M) {
235 self.msg = Some(msg.to_string());
236 }
237
238 pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
240 self.msg = Some(msg.to_string());
241 self
242 }
243
244 pub fn set_note<N: ToString>(&mut self, note: N) {
246 self.note = Some(note.to_string());
247 }
248
249 pub fn with_note<N: ToString>(mut self, note: N) -> Self {
251 self.set_note(note);
252 self
253 }
254
255 pub fn set_help<N: ToString>(&mut self, note: N) {
257 self.help = Some(note.to_string());
258 }
259
260 pub fn with_help<N: ToString>(mut self, note: N) -> Self {
262 self.set_help(note);
263 self
264 }
265
266 pub fn add_label(&mut self, label: Label<S>) {
268 self.add_labels(std::iter::once(label));
269 }
270
271 pub fn add_labels<L: IntoIterator<Item = Label<S>>>(&mut self, labels: L) {
273 let config = &self.config; self.labels.extend(labels.into_iter().map(|mut label| { label.color = config.filter_color(label.color); label }));
275 }
276
277 pub fn with_label(mut self, label: Label<S>) -> Self {
279 self.add_label(label);
280 self
281 }
282
283 pub fn with_labels<L: IntoIterator<Item = Label<S>>>(mut self, labels: L) -> Self {
285 self.add_labels(labels);
286 self
287 }
288
289 pub fn with_config(mut self, config: Config) -> Self {
291 self.config = config;
292 self
293 }
294
295 pub fn finish(self) -> Report<'a, S> {
297 Report {
298 kind: self.kind,
299 code: self.code,
300 msg: self.msg,
301 note: self.note,
302 help: self.help,
303 location: self.location,
304 labels: self.labels,
305 config: self.config,
306 }
307 }
308}
309
310impl<'a, S: Span> fmt::Debug for ReportBuilder<'a, S> {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 f.debug_struct("ReportBuilder")
313 .field("kind", &self.kind)
314 .field("code", &self.code)
315 .field("msg", &self.msg)
316 .field("note", &self.note)
317 .field("help", &self.help)
318 .field("config", &self.config)
319 .finish()
320 }
321}
322
323#[derive(Copy, Clone, Debug, PartialEq, Eq)]
325pub enum LabelAttach {
326 Start,
328 Middle,
330 End,
332}
333
334#[derive(Copy, Clone, Debug, PartialEq, Eq)]
336pub enum CharSet {
337 Unicode,
339 Ascii,
341}
342
343#[derive(Copy, Clone, Debug, PartialEq, Eq)]
345pub struct Config {
346 cross_gap: bool,
347 label_attach: LabelAttach,
348 compact: bool,
349 underlines: bool,
350 multiline_arrows: bool,
351 color: bool,
352 tab_width: usize,
353 char_set: CharSet,
354}
355
356impl Config {
357 pub fn with_cross_gap(mut self, cross_gap: bool) -> Self { self.cross_gap = cross_gap; self }
363 pub fn with_label_attach(mut self, label_attach: LabelAttach) -> Self { self.label_attach = label_attach; self }
367 pub fn with_compact(mut self, compact: bool) -> Self { self.compact = compact; self }
371 pub fn with_underlines(mut self, underlines: bool) -> Self { self.underlines = underlines; self }
375 pub fn with_multiline_arrows(mut self, multiline_arrows: bool) -> Self { self.multiline_arrows = multiline_arrows; self }
379 pub fn with_color(mut self, color: bool) -> Self { self.color = color; self }
383 pub fn with_tab_width(mut self, tab_width: usize) -> Self { self.tab_width = tab_width; self }
387 pub fn with_char_set(mut self, char_set: CharSet) -> Self { self.char_set = char_set; self }
391
392 fn error_color(&self) -> Option<Color> { Some(Color::Red).filter(|_| self.color) }
393 fn warning_color(&self) -> Option<Color> { Some(Color::Yellow).filter(|_| self.color) }
394 fn advice_color(&self) -> Option<Color> { Some(Color::Fixed(147)).filter(|_| self.color) }
395 fn margin_color(&self) -> Option<Color> { Some(Color::Fixed(246)).filter(|_| self.color) }
396 fn skipped_margin_color(&self) -> Option<Color> { Some(Color::Fixed(240)).filter(|_| self.color) }
397 fn unimportant_color(&self) -> Option<Color> { Some(Color::Fixed(249)).filter(|_| self.color) }
398 fn note_color(&self) -> Option<Color> { Some(Color::Fixed(115)).filter(|_| self.color) }
399 fn filter_color(&self, color: Option<Color>) -> Option<Color> { color.filter(|_| self.color) }
400
401 fn char_width(&self, c: char, col: usize) -> (char, usize) {
403 match c {
404 '\t' => {
405 let tab_end = (col / self.tab_width + 1) * self.tab_width;
407 (' ', tab_end - col)
408 },
409 c if c.is_whitespace() => (' ', 1),
410 _ => (c, c.width().unwrap_or(1)),
411 }
412 }
413}
414
415impl Default for Config {
416 fn default() -> Self {
417 Self {
418 cross_gap: true,
419 label_attach: LabelAttach::Middle,
420 compact: false,
421 underlines: true,
422 multiline_arrows: true,
423 color: true,
424 tab_width: 4,
425 char_set: CharSet::Unicode,
426 }
427 }
428}