#![doc(html_root_url = "https://docs.rs/color-spantrace/0.1.6")]
#![warn(
missing_debug_implementations,
missing_docs,
missing_doc_code_examples,
rust_2018_idioms,
unreachable_pub,
bad_style,
const_err,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_in_public,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true
)]
use once_cell::sync::OnceCell;
use owo_colors::{style, Style};
use std::env;
use std::fmt;
use std::fs::File;
use std::io::{BufRead, BufReader};
use tracing_error::SpanTrace;
static THEME: OnceCell<Theme> = OnceCell::new();
#[derive(Debug, Copy, Clone, Default)]
pub struct Theme {
file: Style,
line_number: Style,
target: Style,
fields: Style,
active_line: Style,
}
impl Theme {
pub fn new() -> Self {
Self::default()
}
pub fn dark() -> Self {
Self {
file: style().purple(),
line_number: style().purple(),
active_line: style().white().bold(),
target: style().bright_red(),
fields: style().bright_cyan(),
}
}
pub fn light() -> Self {
Self {
file: style().purple(),
line_number: style().purple(),
target: style().red(),
fields: style().blue(),
active_line: style().bold(),
}
}
pub fn file(mut self, style: Style) -> Self {
self.file = style;
self
}
pub fn line_number(mut self, style: Style) -> Self {
self.line_number = style;
self
}
pub fn target(mut self, style: Style) -> Self {
self.target = style;
self
}
pub fn fields(mut self, style: Style) -> Self {
self.fields = style;
self
}
pub fn active_line(mut self, style: Style) -> Self {
self.active_line = style;
self
}
}
#[derive(Debug)]
pub struct InstallThemeError;
impl fmt::Display for InstallThemeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("could not set the provided `Theme` globally as another was already set")
}
}
impl std::error::Error for InstallThemeError {}
pub fn set_theme(theme: Theme) -> Result<(), InstallThemeError> {
THEME.set(theme).map_err(|_| InstallThemeError)
}
pub fn colorize(span_trace: &SpanTrace) -> impl fmt::Display + '_ {
let theme = *THEME.get_or_init(Theme::dark);
ColorSpanTrace { span_trace, theme }
}
struct ColorSpanTrace<'a> {
span_trace: &'a SpanTrace,
theme: Theme,
}
macro_rules! try_bool {
($e:expr, $dest:ident) => {{
let ret = $e.unwrap_or_else(|e| $dest = Err(e));
if $dest.is_err() {
return false;
}
ret
}};
}
struct Frame<'a> {
metadata: &'a tracing_core::Metadata<'static>,
fields: &'a str,
theme: Theme,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Verbosity {
Minimal,
Medium,
Full,
}
impl Verbosity {
fn lib_from_env() -> Self {
Self::convert_env(
env::var("RUST_LIB_BACKTRACE")
.or_else(|_| env::var("RUST_BACKTRACE"))
.ok(),
)
}
fn convert_env(env: Option<String>) -> Self {
match env {
Some(ref x) if x == "full" => Verbosity::Full,
Some(_) => Verbosity::Medium,
None => Verbosity::Minimal,
}
}
}
impl Frame<'_> {
fn print(&self, i: u32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.print_header(i, f)?;
self.print_fields(f)?;
self.print_source_location(f)?;
Ok(())
}
fn print_header(&self, i: u32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:>2}: {}{}{}",
i,
self.theme.target.style(self.metadata.target()),
self.theme.target.style("::"),
self.theme.target.style(self.metadata.name()),
)
}
fn print_fields(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.fields.is_empty() {
write!(f, " with {}", self.theme.fields.style(self.fields))?;
}
Ok(())
}
fn print_source_location(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(file) = self.metadata.file() {
let lineno = self
.metadata
.line()
.map_or("<unknown line>".to_owned(), |x| x.to_string());
write!(
f,
"\n at {}:{}",
self.theme.file.style(file),
self.theme.line_number.style(lineno),
)?;
} else {
write!(f, "\n at <unknown source file>")?;
}
Ok(())
}
fn print_source_if_avail(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (lineno, filename) = match (self.metadata.line(), self.metadata.file()) {
(Some(a), Some(b)) => (a, b),
_ => return Ok(()),
};
let file = match File::open(filename) {
Ok(file) => file,
Err(_) => return Ok(()),
};
use std::fmt::Write;
let reader = BufReader::new(file);
let start_line = lineno - 2.min(lineno - 1);
let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
let mut buf = String::new();
for (line, cur_line_no) in surrounding_src.zip(start_line..) {
if cur_line_no == lineno {
write!(
&mut buf,
"{:>8} > {}",
cur_line_no.to_string(),
line.unwrap()
)?;
write!(f, "\n{}", self.theme.active_line.style(&buf))?;
buf.clear();
} else {
write!(f, "\n{:>8} │ {}", cur_line_no, line.unwrap())?;
}
}
Ok(())
}
}
impl fmt::Display for ColorSpanTrace<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut err = Ok(());
let mut span = 0;
writeln!(f, "{:━^80}\n", " SPANTRACE ")?;
self.span_trace.with_spans(|metadata, fields| {
let frame = Frame {
metadata,
fields,
theme: self.theme,
};
if span > 0 {
try_bool!(write!(f, "\n",), err);
}
try_bool!(frame.print(span, f), err);
if Verbosity::lib_from_env() == Verbosity::Full {
try_bool!(frame.print_source_if_avail(f), err);
}
span += 1;
true
});
err
}
}