use crate::linter::LintFailed;
use colored::*;
use failure::{Error, ResultExt};
use std::cmp;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use term::{self, color, StdoutTerminal};
#[derive(PartialEq)]
#[allow(dead_code)]
enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
Reset,
}
static CHAR_CR: u8 = 0x0d;
static CHAR_LF: u8 = 0x0a;
pub struct Printer {
term: Option<Box<StdoutTerminal>>,
}
impl Printer {
#[cfg_attr(tarpaulin, skip)]
pub fn new() -> Printer {
Printer {
term: term::stdout(),
}
}
#[cfg_attr(tarpaulin, skip)]
pub fn print(&mut self, failed: &LintFailed, single: bool) -> Result<(), Error> {
if single {
self.print_single(failed)?;
} else {
self.print_pretty(failed)?;
}
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
fn write(&mut self, dat: &str, color: Color) {
if let Some(ref mut term) = self.term {
let term_color = match color {
Color::Black => color::BLACK,
Color::Red => color::RED,
Color::Green => color::GREEN,
Color::Yellow => color::YELLOW,
Color::Blue => color::BLUE,
Color::Magenta => color::MAGENTA,
Color::Cyan => color::CYAN,
Color::White => color::WHITE,
Color::BrightBlack => color::BRIGHT_BLACK,
Color::BrightRed => color::BRIGHT_RED,
Color::BrightGreen => color::BRIGHT_GREEN,
Color::BrightYellow => color::BRIGHT_YELLOW,
Color::BrightBlue => color::BRIGHT_BLUE,
Color::BrightMagenta => color::BRIGHT_MAGENTA,
Color::BrightCyan => color::BRIGHT_CYAN,
Color::BrightWhite => color::BRIGHT_WHITE,
Color::Reset => color::BLACK,
};
if color == Color::Reset {
let _ = term.reset();
} else {
let _ = term.fg(term_color);
}
let _ = write!(term, "{}", dat);
} else {
let colored = match color {
Color::Black => dat.black(),
Color::Red => dat.red(),
Color::Green => dat.green(),
Color::Yellow => dat.yellow(),
Color::Blue => dat.blue(),
Color::Magenta => dat.magenta(),
Color::Cyan => dat.cyan(),
Color::White => dat.white(),
Color::BrightBlack => dat.bright_black(),
Color::BrightRed => dat.bright_red(),
Color::BrightGreen => dat.bright_green(),
Color::BrightYellow => dat.bright_yellow(),
Color::BrightBlue => dat.bright_blue(),
Color::BrightMagenta => dat.bright_magenta(),
Color::BrightCyan => dat.bright_cyan(),
Color::BrightWhite => dat.bright_white(),
Color::Reset => dat.clear(),
};
print!("{}", colored);
}
}
#[cfg_attr(tarpaulin, skip)]
fn print_single(&mut self, failed: &LintFailed) -> Result<(), Error> {
let mut f = File::open(&failed.path)
.with_context(|_| format!("failed to open: '{}'", failed.path.to_string_lossy()))?;
let mut s = String::new();
let _ = f.read_to_string(&mut s);
let mut pos = 0;
let mut column = 1;
let mut last_lf = 0;
while pos < s.len() {
if s.as_bytes()[pos] == CHAR_LF {
column += 1;
last_lf = pos;
}
if failed.beg == pos {
let row = pos - last_lf;
let mut next_crlf = pos;
while next_crlf < s.len() {
if s.as_bytes()[next_crlf] == CHAR_CR || s.as_bytes()[next_crlf] == CHAR_LF {
break;
}
next_crlf += 1;
}
self.write("Fail", Color::BrightRed);
self.write(
&format!("\t{}:{}:{}", failed.path.to_string_lossy(), column, row),
Color::BrightBlue,
);
self.write(
&format!(
"\t{}",
String::from_utf8_lossy(&s.as_bytes()[pos..next_crlf])
),
Color::White,
);
self.write(&format!("\thint: {}", failed.hint), Color::BrightYellow);
self.write("\n", Color::Reset);
}
pos += 1;
}
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
fn print_pretty(&mut self, failed: &LintFailed) -> Result<(), Error> {
let mut f = File::open(&failed.path)
.with_context(|_| format!("failed to open: '{}'", failed.path.to_string_lossy()))?;
let mut s = String::new();
let _ = f.read_to_string(&mut s);
let mut pos = 0;
let mut column = 1;
let mut last_lf = None;
while pos < s.len() {
if s.as_bytes()[pos] == CHAR_LF {
column += 1;
last_lf = Some(pos);
}
if failed.beg == pos {
let row = if let Some(last_lf) = last_lf {
pos - last_lf
} else {
pos + 1
};
let mut next_crlf = pos;
while next_crlf < s.len() {
if s.as_bytes()[next_crlf] == CHAR_CR || s.as_bytes()[next_crlf] == CHAR_LF {
break;
}
next_crlf += 1;
}
self.write("Fail", Color::BrightRed);
let column_len = format!("{}", column).len();
self.write(&format!(": {}\n", failed.name), Color::BrightWhite);
self.write(" -->", Color::BrightBlue);
self.write(
&format!(" {}:{}:{}\n", failed.path.to_string_lossy(), column, row),
Color::White,
);
self.write(
&format!("{}|\n", " ".repeat(column_len + 1)),
Color::BrightBlue,
);
self.write(&format!("{} |", column), Color::BrightBlue);
let beg = if let Some(last_lf) = last_lf {
last_lf + 1
} else {
0
};
self.write(
&format!(
" {}\n",
String::from_utf8_lossy(&s.as_bytes()[beg..next_crlf])
),
Color::White,
);
self.write(
&format!("{}|", " ".repeat(column_len + 1)),
Color::BrightBlue,
);
self.write(
&format!(
" {}{}",
" ".repeat(pos - beg),
"^".repeat(cmp::min(failed.beg + failed.len, next_crlf) - failed.beg)
),
Color::BrightYellow,
);
self.write(&format!(" hint : {}\n", failed.hint), Color::BrightYellow);
self.write(
&format!("{}|", " ".repeat(column_len + 1)),
Color::BrightBlue,
);
self.write(
&format!(
" {}{}",
" ".repeat(pos - beg),
" ".repeat(cmp::min(failed.beg + failed.len, next_crlf) - failed.beg)
),
Color::Yellow,
);
self.write(&format!(" reason: {}\n", failed.reason), Color::Yellow);
self.write("\n", Color::Reset);
}
pos += 1;
}
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_parse_error(&mut self, path: &Path, error_pos: usize) -> Result<(), Error> {
let mut f = File::open(path)
.with_context(|_| format!("failed to open: '{}'", path.to_string_lossy()))?;
let mut s = String::new();
let _ = f.read_to_string(&mut s);
let mut pos = 0;
let mut column = 1;
let mut last_lf = None;
while pos < s.len() {
if s.as_bytes()[pos] == CHAR_LF {
column += 1;
last_lf = Some(pos);
}
if error_pos == pos {
let row = if let Some(last_lf) = last_lf {
pos - last_lf
} else {
pos + 1
};
let mut next_crlf = pos;
while next_crlf < s.len() {
if s.as_bytes()[next_crlf] == CHAR_CR || s.as_bytes()[next_crlf] == CHAR_LF {
break;
}
next_crlf += 1;
}
self.write("Error", Color::BrightRed);
let column_len = format!("{}", column).len();
self.write(&format!(": parse error\n"), Color::BrightWhite);
self.write(" -->", Color::BrightBlue);
self.write(
&format!(" {}:{}:{}\n", path.to_string_lossy(), column, row),
Color::White,
);
self.write(
&format!("{}|\n", " ".repeat(column_len + 1)),
Color::BrightBlue,
);
self.write(&format!("{} |", column), Color::BrightBlue);
let beg = if let Some(last_lf) = last_lf {
last_lf + 1
} else {
0
};
self.write(
&format!(
" {}\n",
String::from_utf8_lossy(&s.as_bytes()[beg..next_crlf])
),
Color::White,
);
self.write(
&format!("{}|", " ".repeat(column_len + 1)),
Color::BrightBlue,
);
self.write(
&format!(
" {}{}",
" ".repeat(pos - beg),
"^".repeat(cmp::min(error_pos + 1, next_crlf) - error_pos)
),
Color::BrightYellow,
);
self.write("\n\n", Color::Reset);
}
pos += 1;
}
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_error(&mut self, error: &str) -> Result<(), Error> {
self.write("Error", Color::BrightRed);
self.write(&format!(": {}", error), Color::BrightWhite);
self.write("\n", Color::Reset);
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_info(&mut self, error: &str) -> Result<(), Error> {
self.write("Info", Color::BrightGreen);
self.write(&format!(": {}", error), Color::BrightWhite);
self.write("\n", Color::Reset);
Ok(())
}
}