#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
mod source;
mod display;
mod draw;
mod write;
pub use crate::{
source::{Line, Source, Cache, FileCache, FnCache, sources},
draw::{Fmt, ColorGenerator},
};
pub use yansi::Color;
#[cfg(any(feature = "concolor", doc))]
pub use crate::draw::StdoutFmt;
use crate::display::*;
use std::{
ops::Range,
io::{self, Write},
hash::Hash,
cmp::{PartialEq, Eq},
fmt,
};
use unicode_width::UnicodeWidthChar;
pub trait Span {
type SourceId: PartialEq + ToOwned + ?Sized;
fn source(&self) -> &Self::SourceId;
fn start(&self) -> usize;
fn end(&self) -> usize;
fn len(&self) -> usize { self.end().saturating_sub(self.start()) }
fn contains(&self, offset: usize) -> bool { (self.start()..self.end()).contains(&offset) }
}
impl Span for Range<usize> {
type SourceId = ();
fn source(&self) -> &Self::SourceId { &() }
fn start(&self) -> usize { self.start }
fn end(&self) -> usize { self.end }
}
impl<Id: fmt::Debug + Hash + PartialEq + Eq + ToOwned> Span for (Id, Range<usize>) {
type SourceId = Id;
fn source(&self) -> &Self::SourceId { &self.0 }
fn start(&self) -> usize { self.1.start }
fn end(&self) -> usize { self.1.end }
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Label<S = Range<usize>> {
span: S,
msg: Option<String>,
color: Option<Color>,
order: i32,
priority: i32,
}
impl<S> Label<S> {
pub fn new(span: S) -> Self {
Self {
span,
msg: None,
color: None,
order: 0,
priority: 0,
}
}
pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
self.msg = Some(msg.to_string());
self
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn with_order(mut self, order: i32) -> Self {
self.order = order;
self
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
}
pub struct Report<'a, S: Span = Range<usize>> {
kind: ReportKind<'a>,
code: Option<String>,
msg: Option<String>,
note: Option<String>,
help: Option<String>,
location: (<S::SourceId as ToOwned>::Owned, usize),
labels: Vec<Label<S>>,
config: Config,
}
impl<S: Span> Report<'_, S> {
pub fn build<Id: Into<<S::SourceId as ToOwned>::Owned>>(kind: ReportKind, src_id: Id, offset: usize) -> ReportBuilder<S> {
ReportBuilder {
kind,
code: None,
msg: None,
note: None,
help: None,
location: (src_id.into(), offset),
labels: Vec::new(),
config: Config::default(),
}
}
pub fn eprint<C: Cache<S::SourceId>>(&self, cache: C) -> io::Result<()> {
self.write(cache, io::stderr())
}
pub fn print<C: Cache<S::SourceId>>(&self, cache: C) -> io::Result<()> {
self.write_for_stdout(cache, io::stdout())
}
}
impl<'a, S: Span> fmt::Debug for Report<'a, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Report")
.field("kind", &self.kind)
.field("code", &self.code)
.field("msg", &self.msg)
.field("note", &self.note)
.field("help", &self.help)
.field("config", &self.config)
.finish()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ReportKind<'a> {
Error,
Warning,
Advice,
Custom(&'a str, Color),
}
impl fmt::Display for ReportKind<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ReportKind::Error => write!(f, "Error"),
ReportKind::Warning => write!(f, "Warning"),
ReportKind::Advice => write!(f, "Advice"),
ReportKind::Custom(s, _) => write!(f, "{}", s),
}
}
}
pub struct ReportBuilder<'a, S: Span> {
kind: ReportKind<'a>,
code: Option<String>,
msg: Option<String>,
note: Option<String>,
help: Option<String>,
location: (<S::SourceId as ToOwned>::Owned, usize),
labels: Vec<Label<S>>,
config: Config,
}
impl<'a, S: Span> ReportBuilder<'a, S> {
pub fn with_code<C: fmt::Display>(mut self, code: C) -> Self {
self.code = Some(format!("{:02}", code));
self
}
pub fn set_message<M: ToString>(&mut self, msg: M) {
self.msg = Some(msg.to_string());
}
pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
self.msg = Some(msg.to_string());
self
}
pub fn set_note<N: ToString>(&mut self, note: N) {
self.note = Some(note.to_string());
}
pub fn with_note<N: ToString>(mut self, note: N) -> Self {
self.set_note(note);
self
}
pub fn set_help<N: ToString>(&mut self, note: N) {
self.help = Some(note.to_string());
}
pub fn with_help<N: ToString>(mut self, note: N) -> Self {
self.set_help(note);
self
}
pub fn add_label(&mut self, label: Label<S>) {
self.add_labels(std::iter::once(label));
}
pub fn add_labels<L: IntoIterator<Item = Label<S>>>(&mut self, labels: L) {
let config = &self.config; self.labels.extend(labels.into_iter().map(|mut label| { label.color = config.filter_color(label.color); label }));
}
pub fn with_label(mut self, label: Label<S>) -> Self {
self.add_label(label);
self
}
pub fn with_labels<L: IntoIterator<Item = Label<S>>>(mut self, labels: L) -> Self {
self.add_labels(labels);
self
}
pub fn with_config(mut self, config: Config) -> Self {
self.config = config;
self
}
pub fn finish(self) -> Report<'a, S> {
Report {
kind: self.kind,
code: self.code,
msg: self.msg,
note: self.note,
help: self.help,
location: self.location,
labels: self.labels,
config: self.config,
}
}
}
impl<'a, S: Span> fmt::Debug for ReportBuilder<'a, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReportBuilder")
.field("kind", &self.kind)
.field("code", &self.code)
.field("msg", &self.msg)
.field("note", &self.note)
.field("help", &self.help)
.field("config", &self.config)
.finish()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LabelAttach {
Start,
Middle,
End,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CharSet {
Unicode,
Ascii,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Config {
cross_gap: bool,
label_attach: LabelAttach,
compact: bool,
underlines: bool,
multiline_arrows: bool,
color: bool,
tab_width: usize,
char_set: CharSet,
}
impl Config {
pub fn with_cross_gap(mut self, cross_gap: bool) -> Self { self.cross_gap = cross_gap; self }
pub fn with_label_attach(mut self, label_attach: LabelAttach) -> Self { self.label_attach = label_attach; self }
pub fn with_compact(mut self, compact: bool) -> Self { self.compact = compact; self }
pub fn with_underlines(mut self, underlines: bool) -> Self { self.underlines = underlines; self }
pub fn with_multiline_arrows(mut self, multiline_arrows: bool) -> Self { self.multiline_arrows = multiline_arrows; self }
pub fn with_color(mut self, color: bool) -> Self { self.color = color; self }
pub fn with_tab_width(mut self, tab_width: usize) -> Self { self.tab_width = tab_width; self }
pub fn with_char_set(mut self, char_set: CharSet) -> Self { self.char_set = char_set; self }
fn error_color(&self) -> Option<Color> { Some(Color::Red).filter(|_| self.color) }
fn warning_color(&self) -> Option<Color> { Some(Color::Yellow).filter(|_| self.color) }
fn advice_color(&self) -> Option<Color> { Some(Color::Fixed(147)).filter(|_| self.color) }
fn margin_color(&self) -> Option<Color> { Some(Color::Fixed(246)).filter(|_| self.color) }
fn skipped_margin_color(&self) -> Option<Color> { Some(Color::Fixed(240)).filter(|_| self.color) }
fn unimportant_color(&self) -> Option<Color> { Some(Color::Fixed(249)).filter(|_| self.color) }
fn note_color(&self) -> Option<Color> { Some(Color::Fixed(115)).filter(|_| self.color) }
fn filter_color(&self, color: Option<Color>) -> Option<Color> { color.filter(|_| self.color) }
fn char_width(&self, c: char, col: usize) -> (char, usize) {
match c {
'\t' => {
let tab_end = (col / self.tab_width + 1) * self.tab_width;
(' ', tab_end - col)
},
c if c.is_whitespace() => (' ', 1),
_ => (c, c.width().unwrap_or(1)),
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
cross_gap: true,
label_attach: LabelAttach::Middle,
compact: false,
underlines: true,
multiline_arrows: true,
color: true,
tab_width: 4,
char_set: CharSet::Unicode,
}
}
}