use crate::linter::LintFailed;
use anyhow::{Context, Error};
use colored::*;
use std::cmp;
use std::fs::File;
use std::io::{Read, Write};
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,
}
enum TermCapture<C, N> {
Capturable(C),
Noncapturable(N),
}
static CHAR_CR: u8 = 0x0d;
static CHAR_LF: u8 = 0x0a;
pub struct Printer {
term: TermCapture<Vec<u8>, Option<Box<StdoutTerminal>>>,
}
impl Printer {
#[cfg_attr(tarpaulin, skip)]
pub fn new(capturable: bool) -> Printer {
Printer {
term: if capturable {
TermCapture::Capturable(Vec::new())
} else {
TermCapture::Noncapturable(term::stdout())
},
}
}
pub fn read_to_string(&self) -> Option<String> {
match self.term {
TermCapture::Capturable(ref buf) => Some(String::from_utf8_lossy(buf).to_string()),
_ => None,
}
}
#[cfg_attr(tarpaulin, skip)]
fn write(&mut self, dat: &str, color: Option<Color>) {
match self.term {
TermCapture::Noncapturable(Some(ref mut term)) => {
if let Some(c) = color {
if c == Color::Reset {
let _ = term.reset();
} else {
let term_color = match c {
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,
};
let _ = term.fg(term_color);
}
}
let _ = write!(term, "{}", dat);
}
TermCapture::Noncapturable(None) => {
if let Some(c) = color {
let colored = match c {
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);
} else {
print!("{}", dat);
}
}
TermCapture::Capturable(ref mut buf) => {
let _ = write!(buf, "{}", dat);
}
}
}
#[cfg_attr(tarpaulin, skip)]
fn with_pos<F: FnMut(usize, usize, usize, usize, Option<usize>)>(
src: &str,
print_pos: usize,
mut func: F,
) {
let mut pos = 0;
let mut column = 1;
let mut last_lf = None;
while pos < src.len() {
if src.as_bytes()[pos] == CHAR_LF {
column += 1;
last_lf = Some(pos);
}
if print_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 < src.len() {
if src.as_bytes()[next_crlf] == CHAR_CR || src.as_bytes()[next_crlf] == CHAR_LF
{
break;
}
next_crlf += 1;
}
func(pos, column, row, next_crlf, last_lf);
}
pos += 1;
}
}
fn get_pos(src: &str, print_pos: usize) -> Option<(usize, usize)> {
let mut pos = 0;
let mut column = 1;
let mut last_lf = None;
while pos < src.len() {
if src.as_bytes()[pos] == CHAR_LF {
column += 1;
last_lf = Some(pos);
}
if print_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 < src.len() {
if src.as_bytes()[next_crlf] == CHAR_CR || src.as_bytes()[next_crlf] == CHAR_LF
{
break;
}
next_crlf += 1;
}
return Some((row, column));
}
pos += 1;
}
None
}
#[cfg_attr(tarpaulin, skip)]
fn print_oneline(
&mut self,
src: &str,
print_pos: usize,
header: &str,
path: &Path,
hint: Option<&str>,
) {
Printer::with_pos(src, print_pos, |pos, column, row, next_crlf, _last_lf| {
self.write(header, Some(Color::BrightRed));
self.write(
&format!("\t{}:{}:{}", path.to_string_lossy(), column, row),
Some(Color::BrightBlue),
);
self.write(
&format!(
"\t{}",
String::from_utf8_lossy(&src.as_bytes()[pos..next_crlf])
),
Some(Color::White),
);
if let Some(hint) = hint {
self.write(&format!("\thint: {}", hint), Some(Color::BrightYellow));
}
self.write("\n", Some(Color::Reset));
});
}
#[cfg_attr(tarpaulin, skip)]
fn print_pretty(
&mut self,
src: &str,
print_pos: usize,
print_len: usize,
header: &str,
description: &str,
path: &Path,
hint: Option<&str>,
reason: Option<&str>,
) {
Printer::with_pos(src, print_pos, |pos, column, row, next_crlf, last_lf| {
self.write(header, Some(Color::BrightRed));
let beg = if let Some(last_lf) = last_lf {
if next_crlf > last_lf {
last_lf + 1
} else {
next_crlf
}
} else {
0
};
let column = if beg == 0 && next_crlf == 0 {
0
} else {
column
};
let column_len = format!("{}", column).len();
self.write(&format!(": {}\n", description), Some(Color::BrightWhite));
self.write(" -->", Some(Color::BrightBlue));
self.write(
&format!(" {}:{}:{}\n", path.to_string_lossy(), column, row),
Some(Color::White),
);
self.write(
&format!("{}|\n", " ".repeat(column_len + 1)),
Some(Color::BrightBlue),
);
self.write(&format!("{} |", column), Some(Color::BrightBlue));
let line = String::from_utf8_lossy(&src.as_bytes()[beg..next_crlf]);
self.write(&format!(" {}\n", line), Some(Color::White));
self.write(
&format!("{}|", " ".repeat(column_len + 1)),
Some(Color::BrightBlue),
);
let hr_indent = String::from_utf8_lossy(&src.as_bytes()[beg..pos]).chars().count();
self.write(
&format!(
" {}{}",
" ".repeat(hr_indent),
"^".repeat(cmp::min(print_pos + print_len, next_crlf) - print_pos)
),
Some(Color::BrightYellow),
);
if let Some(hint) = hint {
self.write(&format!(" hint : {}\n", hint), Some(Color::BrightYellow));
}
if let Some(reason) = reason {
self.write(
&format!("{}|", " ".repeat(column_len + 1)),
Some(Color::BrightBlue),
);
self.write(
&format!(
" {}{}",
" ".repeat(hr_indent),
" ".repeat(cmp::min(print_pos + print_len, next_crlf) - print_pos)
),
Some(Color::Yellow),
);
self.write(&format!(" reason: {}\n", reason), Some(Color::Yellow));
}
self.write("\n", Some(Color::Reset));
});
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_failed(
&mut self,
failed: &LintFailed,
oneline: bool,
github_actions: bool,
) -> 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);
if oneline {
self.print_oneline(&s, failed.beg, "Fail", &failed.path, Some(&failed.hint));
} else {
self.print_pretty(
&s,
failed.beg,
failed.len,
"Fail",
&failed.name,
&failed.path,
Some(&failed.hint),
Some(&failed.reason),
);
}
if github_actions {
if let Some((row, column)) = Printer::get_pos(&s, failed.beg) {
println!(
"::error file={},line={},col={}::{}",
failed.path.to_string_lossy(),
column,
row,
failed.hint
);
}
}
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_parse_error(
&mut self,
path: &Path,
error_pos: usize,
oneline: bool,
) -> 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);
if oneline {
self.print_oneline(&s, error_pos, "Error", path, Some("parse error"));
} else {
self.print_pretty(&s, error_pos, 1, "Error", "parse error", path, None, None);
}
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_preprocess_error(
&mut self,
path: &Path,
error_pos: usize,
oneline: bool,
) -> 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);
if oneline {
self.print_oneline(&s, error_pos, "Error", path, Some("preprocess error"));
} else {
self.print_pretty(
&s,
error_pos,
1,
"Error",
"preprocess error",
path,
None,
None,
);
}
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_error(&mut self, msg: &str) -> Result<(), Error> {
self.write("Error", Some(Color::BrightRed));
self.write(&format!(": {}", msg), Some(Color::BrightWhite));
self.write("\n", Some(Color::Reset));
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_warning(&mut self, msg: &str) -> Result<(), Error> {
self.write("Warning", Some(Color::BrightYellow));
self.write(&format!(": {}", msg), Some(Color::BrightWhite));
self.write("\n", Some(Color::Reset));
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_info(&mut self, msg: &str) -> Result<(), Error> {
self.write("Info", Some(Color::BrightGreen));
self.write(&format!(": {}", msg), Some(Color::BrightWhite));
self.write("\n", Some(Color::Reset));
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print_error_type(&mut self, error: Error) -> Result<(), Error> {
let mut cause = error.chain();
self.write("Error", Some(Color::BrightRed));
self.write(&format!(": {}", cause.next().unwrap()), Some(Color::BrightWhite));
self.write("\n", Some(Color::Reset));
for x in cause {
self.write(&format!(" caused by: {}\n", x), Some(Color::Reset));
}
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn print(&mut self, msg: &str) -> Result<(), Error> {
self.write(msg, None);
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
pub fn println(&mut self, msg: &str) -> Result<(), Error> {
self.write(msg, None);
self.write("\n", None);
Ok(())
}
}