1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3
4mod display;
5mod draw;
6mod source;
7mod write;
8
9pub use crate::{
10 draw::{ColorGenerator, Fmt},
11 source::{sources, Cache, FileCache, FnCache, Line, Source},
12};
13pub use yansi::Color;
14
15use crate::display::*;
16use std::{
17 cmp::{Eq, PartialEq},
18 fmt,
19 hash::Hash,
20 io::{self, Write},
21 ops::Range,
22};
23
24pub trait Span {
26 type SourceId: PartialEq + ToOwned + ?Sized;
28
29 fn source(&self) -> &Self::SourceId;
31
32 fn start(&self) -> usize;
36
37 fn end(&self) -> usize;
43
44 fn len(&self) -> usize {
46 self.end().saturating_sub(self.start())
47 }
48
49 fn contains(&self, offset: usize) -> bool {
51 (self.start()..self.end()).contains(&offset)
52 }
53}
54
55impl Span for Range<usize> {
56 type SourceId = ();
57
58 fn source(&self) -> &Self::SourceId {
59 &()
60 }
61 fn start(&self) -> usize {
62 self.start
63 }
64 fn end(&self) -> usize {
65 self.end
66 }
67}
68
69impl<Id: fmt::Debug + Hash + PartialEq + Eq + ToOwned> Span for (Id, Range<usize>) {
70 type SourceId = Id;
71
72 fn source(&self) -> &Self::SourceId {
73 &self.0
74 }
75 fn start(&self) -> usize {
76 self.1.start
77 }
78 fn end(&self) -> usize {
79 self.1.end
80 }
81}
82
83pub struct Label<S = Range<usize>> {
85 span: S,
86 msg: Option<String>,
87 color: Option<Color>,
88 order: i32,
89 priority: i32,
90}
91
92impl<S> Label<S> {
93 pub fn new(span: S) -> Self {
95 Self {
96 span,
97 msg: None,
98 color: None,
99 order: 0,
100 priority: 0,
101 }
102 }
103
104 pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
106 self.msg = Some(msg.to_string());
107 self
108 }
109
110 pub fn with_color(mut self, color: Color) -> Self {
112 self.color = Some(color);
113 self
114 }
115
116 pub fn with_order(mut self, order: i32) -> Self {
127 self.order = order;
128 self
129 }
130
131 pub fn with_priority(mut self, priority: i32) -> Self {
141 self.priority = priority;
142 self
143 }
144}
145
146pub struct Report<S = Range<usize>, C = Source>
148where
149 S: Span,
150 C: Cache<S::SourceId> + Clone,
151 source::Source: source::Cache<<S as Span>::SourceId>,
152{
153 source: Option<C>,
154 kind: ReportKind,
155 code: Option<String>,
156 msg: Option<String>,
157 note: Option<String>,
158 help: Option<String>,
159 labels: Vec<Label<S>>,
160 config: Config,
161}
162
163impl<S, C> Report<S, C>
164where
165 S: Span,
166 C: Cache<S::SourceId> + Clone,
167 source::Source: source::Cache<<S as Span>::SourceId>,
168{
169 pub fn build(
171 kind: ReportKind,
172 ) -> ReportBuilder<S, C> {
173 ReportBuilder {
174 kind,
175 source: None,
176 code: None,
177 msg: None,
178 note: None,
179 help: None,
180 labels: Vec::new(),
181 config: Config::default(),
182 }
183 }
184
185 pub fn eprint(&self) -> io::Result<()> {
187 self.write(io::stderr())
188 }
189
190 pub fn print(&self) -> io::Result<()> {
195 self.write(io::stdout())
196 }
197}
198
199#[derive(Copy, Clone, Debug, PartialEq, Eq)]
201pub enum ReportKind {
202 Error,
205 Warning,
208 Advice,
210 Custom(&'static str, Color),
212}
213
214impl fmt::Display for ReportKind {
215 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
216 match self {
217 ReportKind::Error => write!(f, "Error"),
218 ReportKind::Warning => write!(f, "Warning"),
219 ReportKind::Advice => write!(f, "Advice"),
220 ReportKind::Custom(s, _) => write!(f, "{}", s),
221 }
222 }
223}
224
225pub struct ReportBuilder<S, C>
227where
228 S: Span,
229 C: Cache<S::SourceId>,
230{
231 source: Option<C>,
232 kind: ReportKind,
233 code: Option<String>,
234 msg: Option<String>,
235 note: Option<String>,
236 help: Option<String>,
237 labels: Vec<Label<S>>,
238 config: Config,
239}
240
241impl<S, C> ReportBuilder<S, C>
242where
243 S: Span,
244 C: Cache<S::SourceId> + Clone,
245 source::Source: source::Cache<<S as Span>::SourceId>,
246{
247 pub fn with_code<D: fmt::Display>(mut self, code: D) -> Self {
249 self.code = Some(format!("{:02}", code));
250 self
251 }
252
253 pub fn set_message<M: ToString>(&mut self, msg: M) {
255 self.msg = Some(msg.to_string());
256 }
257
258 pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
260 self.msg = Some(msg.to_string());
261 self
262 }
263
264 pub fn set_note<N: ToString>(&mut self, note: N) {
266 self.note = Some(note.to_string());
267 }
268
269 pub fn with_note<N: ToString>(mut self, note: N) -> Self {
271 self.set_note(note);
272 self
273 }
274
275 pub fn set_help<N: ToString>(&mut self, note: N) {
277 self.help = Some(note.to_string());
278 }
279
280 pub fn with_help<N: ToString>(mut self, note: N) -> Self {
282 self.set_help(note);
283 self
284 }
285
286 pub fn add_label(&mut self, label: Label<S>) {
288 self.labels.push(label);
289 }
290
291 pub fn add_labels<L: IntoIterator<Item = Label<S>>>(&mut self, labels: L) {
293 self.labels.extend(labels);
294 }
295
296 pub fn with_label(mut self, label: Label<S>) -> Self {
298 self.add_label(label);
299 self
300 }
301
302 pub fn with_config(mut self, config: Config) -> Self {
304 self.config = config;
305 self
306 }
307
308 pub fn with_source(mut self, source: C) -> Self {
310 self.source = Some(source);
311 self
312 }
313
314 pub fn finish(self) -> Report<S, C> {
316 Report {
317 source: self.source,
318 kind: self.kind,
319 code: self.code,
320 msg: self.msg,
321 note: self.note,
322 help: self.help,
323 labels: self.labels,
324 config: self.config,
325 }
326 }
327}
328
329#[derive(Copy, Clone, Debug, PartialEq, Eq)]
331pub enum LabelAttach {
332 Start,
334 Middle,
336 End,
338}
339
340#[derive(Copy, Clone, Debug, PartialEq, Eq)]
342pub enum CharSet {
343 Unicode,
345 Ascii,
347}
348
349#[derive(Copy, Clone, Debug, PartialEq, Eq)]
351pub struct Config {
352 cross_gap: bool,
353 label_attach: LabelAttach,
354 compact: bool,
355 underlines: bool,
356 multiline_arrows: bool,
357 color: bool,
358 tab_width: usize,
359 char_set: CharSet,
360}
361
362impl Config {
363 pub fn with_cross_gap(mut self, cross_gap: bool) -> Self {
369 self.cross_gap = cross_gap;
370 self
371 }
372 pub fn with_label_attach(mut self, label_attach: LabelAttach) -> Self {
376 self.label_attach = label_attach;
377 self
378 }
379 pub fn with_compact(mut self, compact: bool) -> Self {
383 self.compact = compact;
384 self
385 }
386 pub fn with_underlines(mut self, underlines: bool) -> Self {
390 self.underlines = underlines;
391 self
392 }
393 pub fn with_multiline_arrows(mut self, multiline_arrows: bool) -> Self {
397 self.multiline_arrows = multiline_arrows;
398 self
399 }
400 pub fn with_color(mut self, color: bool) -> Self {
404 self.color = color;
405 self
406 }
407 pub fn with_tab_width(mut self, tab_width: usize) -> Self {
411 self.tab_width = tab_width;
412 self
413 }
414 pub fn with_char_set(mut self, char_set: CharSet) -> Self {
418 self.char_set = char_set;
419 self
420 }
421
422 fn error_color(&self) -> Option<Color> {
423 Some(Color::Red).filter(|_| self.color)
424 }
425 fn warning_color(&self) -> Option<Color> {
426 Some(Color::Yellow).filter(|_| self.color)
427 }
428 fn advice_color(&self) -> Option<Color> {
429 Some(Color::Fixed(147)).filter(|_| self.color)
430 }
431 fn margin_color(&self) -> Option<Color> {
432 Some(Color::Fixed(246)).filter(|_| self.color)
433 }
434 fn unimportant_color(&self) -> Option<Color> {
435 Some(Color::Fixed(249)).filter(|_| self.color)
436 }
437 fn note_color(&self) -> Option<Color> {
438 Some(Color::Fixed(115)).filter(|_| self.color)
439 }
440
441 fn char_width(&self, c: char, col: usize) -> (char, usize) {
443 match c {
444 '\t' => {
445 let tab_end = (col / self.tab_width + 1) * self.tab_width;
447 (' ', tab_end - col)
448 }
449 c if c.is_whitespace() => (' ', 1),
450 _ => (c, 1),
451 }
452 }
453}
454
455impl Default for Config {
456 fn default() -> Self {
457 Self {
458 cross_gap: true,
459 label_attach: LabelAttach::Middle,
460 compact: false,
461 underlines: true,
462 multiline_arrows: true,
463 color: true,
464 tab_width: 4,
465 char_set: CharSet::Unicode,
466 }
467 }
468}