#![allow(dead_code)]
use colored::{Colorize, Color};
use pad::PadStr;
use crate::compiling::failing::position_info::PositionInfo;
use crate::compiling::failing::message::MessageType;
pub struct Logger {
kind: MessageType,
trace: Vec<PositionInfo>
}
impl Logger {
pub fn new(kind: MessageType, trace: &[PositionInfo]) -> Self {
Logger {
kind,
trace: trace.to_vec()
}
}
fn kind_to_color(&self) -> Color {
match self.kind {
MessageType::Error => Color::Red,
MessageType::Warning => Color::Yellow,
MessageType::Info => Color::Blue
}
}
pub fn header(self, kind: MessageType) -> Self {
let name = match kind {
MessageType::Error => " ERROR ".to_string(),
MessageType::Warning => " WARN ".to_string(),
MessageType::Info => " INFO ".to_string()
};
let formatted = name
.black()
.bold()
.on_color(self.kind_to_color());
eprintln!("{formatted}");
self
}
pub fn text(self, text: Option<String>) -> Self {
if let Some(text) = text {
eprintln!("{}", text.color(self.kind_to_color()));
}
self
}
pub fn padded_text(self, text: Option<String>) -> Self {
if let Some(text) = text {
eprintln!("\n{}", text.color(self.kind_to_color()));
}
self
}
pub fn path(self) -> Self {
let path = match self.trace.first() {
Some(det) => {
[
format!("at {}:{}:{}", det.get_path(), det.row, det.col),
self.trace.iter()
.skip(1)
.map(|det| format!("in {}:{}:{}", det.get_path(), det.row, det.col))
.collect::<Vec<String>>()
.join("\n")
].join("\n")
},
None => {
"at [unknown]:0:0".to_string()
}
};
eprintln!("{}", path.color(self.kind_to_color()).dimmed());
self
}
fn get_row_col_len(&self) -> (usize, usize, usize) {
match self.trace.first() {
Some(loc) => (loc.row, loc.col, loc.len),
None => (0, 0, 0)
}
}
fn get_max_pad_size(&self, length: usize) -> usize {
let (row, _, _) = self.get_row_col_len();
if row < length - 1 {
format!("{}", row + 1).len()
}
else {
format!("{}", row).len()
}
}
fn get_highlighted_part(&self, line: &str) -> [String;3] {
let (_row, col, len) = self.get_row_col_len();
let begin = col - 1;
let end = begin + len;
let mut results: [String; 3] = Default::default();
for (index, letter) in line.chars().enumerate() {
if index < begin {
results[0].push(letter);
}
else if index >= end {
results[2].push(letter);
}
else {
results[1].push(letter);
}
}
results
}
fn get_snippet_row(&self, code: &Vec<String>, index: usize, offset: i8, overflow: &mut usize) -> String {
let (row, col, len) = self.get_row_col_len();
let max_pad = self.get_max_pad_size(code.len());
let index = index as i32 + offset as i32;
let row = row as i32 + offset as i32;
let code = code[index as usize].clone();
let line = format!("{row}").pad_to_width(max_pad);
if offset == 0 {
let slices = self.get_highlighted_part(&code);
let formatted = format!("{}{}{}", slices[0], slices[1].color(self.kind_to_color()), slices[2]);
if col - 1 + len > code.chars().count() {
*overflow = col - 2 + len - code.chars().count();
}
format!("{line}| {formatted}")
}
else {
if *overflow > 0 {
if *overflow > code.chars().count() {
format!("{line}| {}", code.color(self.kind_to_color())).dimmed().to_string()
}
else {
let err = code.get(0..*overflow).unwrap().to_string().color(self.kind_to_color());
let rest = code.get(*overflow..).unwrap().to_string();
format!("{line}| {err}{rest}").dimmed().to_string()
}
}
else {
format!("{line}| {code}").dimmed().to_string()
}
}
}
pub fn snippet<T: AsRef<str>>(self, code: Option<T>) {
let (row, _, _) = self.get_row_col_len();
if let Some(code) = code {
let mut overflow = 0;
let index = row - 1;
let code: String = String::from(code.as_ref());
let code = code.split('\n')
.map(|item| item.to_string())
.collect::<Vec<String>>();
eprintln!();
if index > 0 {
eprintln!("{}", self.get_snippet_row(&code, index, -1, &mut overflow));
}
eprintln!("{}", self.get_snippet_row(&code, index, 0, &mut overflow));
if index < code.len() - 1 {
eprintln!("{}", self.get_snippet_row(&code, index, 1, &mut overflow));
}
}
}
}
#[cfg(test)]
mod test {
#![allow(unused_imports)]
use std::time::Duration;
use std::thread::sleep;
use crate::prelude::{PositionInfo, MessageType};
#[allow(unused_variables)]
#[test]
fn test_displayer() {
let code = vec![
"let a = 12",
"value = 'this",
"is mutltiline",
"code"
].join("\n");
sleep(Duration::from_secs(1));
let trace = [
PositionInfo::at_pos(Some("/path/to/bar".to_string()), (3, 25), 0),
PositionInfo::at_pos(Some("/path/to/foo".to_string()), (2, 9), 24),
];
super::Logger::new(MessageType::Error, &trace)
.header(MessageType::Error)
.text(Some(format!("Cannot call function \"foobar\" on a number")))
.path()
.snippet(Some(code));
}
}